attrs' seems to also not allow mandatory attributes to follow optional one:
In [14]: @attr.s ...: class Baz: ...: a = attr.ib(default=attr.Factory(list)) ...: b = attr.ib() ...: --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-14-2c63f3f229a5> in <module>() ----> 1 @attr.s 2 class Baz: 3 a = attr.ib(default=attr.Factory(list)) 4 b = attr.ib() 5 /Users/waksman/.pyenv/versions/3.6.1/envs/temp/lib/python3.6/site-packages/attr/_make.py in attrs(maybe_cls, these, repr_ns, repr, cmp, hash, init, slots, frozen, str, auto_attribs) 700 return wrap 701 else: --> 702 return wrap(maybe_cls) 703 704 /Users/waksman/.pyenv/versions/3.6.1/envs/temp/lib/python3.6/site-packages/attr/_make.py in wrap(cls) 669 raise TypeError("attrs only works with new-style classes.") 670 --> 671 builder = _ClassBuilder(cls, these, slots, frozen, auto_attribs) 672 673 if repr is True: /Users/waksman/.pyenv/versions/3.6.1/envs/temp/lib/python3.6/site-packages/attr/_make.py in __init__(self, cls, these, slots, frozen, auto_attribs) 369 370 def __init__(self, cls, these, slots, frozen, auto_attribs): --> 371 attrs, super_attrs = _transform_attrs(cls, these, auto_attribs) 372 373 self._cls = cls /Users/waksman/.pyenv/versions/3.6.1/envs/temp/lib/python3.6/site-packages/attr/_make.py in _transform_attrs(cls, these, auto_attribs) 335 "No mandatory attributes allowed after an attribute with a " 336 "default value or factory. Attribute in question: {a!r}" --> 337 .format(a=a) 338 ) 339 elif had_default is False and \ ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='b', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None) On Fri, Jan 26, 2018 at 1:44 PM Guido van Rossum <gu...@python.org> wrote: > What does attrs' solution for this problem look like? > > On Fri, Jan 26, 2018 at 11:11 AM, George Leslie-Waksman <waks...@gmail.com > > wrote: > >> Even if we could inherit the setting, I would think that we would still >> want to require the code be explicit. It seems worse to implicitly require >> keyword only arguments for a class without giving any indication in the >> code. >> >> As it stands, the current implementation does not allow a later subclass >> to be declared without `keyword_only=True` so we could handle this case by >> adding a note to the `TypeError` message about considering the keyword_only >> flag. >> >> How do I got about putting together a proposal to get this into 3.8? >> >> --George >> >> >> On Thu, Jan 25, 2018 at 5:12 AM Eric V. Smith <e...@trueblade.com> wrote: >> >>> I'm not completely opposed to this feature. But there are some cases to >>> consider. Here's the first one that occurs to me: note that due to the >>> way dataclasses work, it would need to be used everywhere down an >>> inheritance hierarchy. That is, if an intermediate base class required >>> it, all class derived from that intermediate base would need to specify >>> it, too. That's because each class just makes decisions based on its >>> fields and its base classes' fields, and not on any flags attached to >>> the base class. As it's currently implemented, a class doesn't remember >>> any of the decorator's arguments, so there's no way to look for this >>> information, anyway. >>> >>> I think there are enough issues here that it's not going to make it in >>> to 3.7. It would require getting a firm proposal together, selling the >>> idea on python-dev, and completing the implementation before Monday. But >>> if you want to try, I'd participate in the discussion. >>> >>> Taking Ivan's suggestion one step further, a way to do this currently is >>> to pass init=False and then write another decorator that adds the >>> kw-only __init__. So the usage would be: >>> >>> @dataclass >>> class Foo: >>> some_default: dict = field(default_factory=dict) >>> >>> @kw_only_init >>> @dataclass(init=False) >>> class Bar(Foo): >>> other_field: int >>> >>> kw_only_init(cls) would look at fields(cls) and construct the __init__. >>> It would be a hassle to re-implement dataclasses's _init_fn function, >>> but it could be made to work (in reality, of course, you'd just copy it >>> and hack it up to do what you want). You'd also need to use some private >>> knowledge of InitVars if you wanted to support them (the stock >>> fields(cls) doesn't return them). >>> >>> For 3.8 we can consider changing dataclasses's APIs if we want to add >>> this. >>> >>> Eric. >>> >>> On 1/25/2018 1:38 AM, George Leslie-Waksman wrote: >>> > It may be possible but it makes for pretty leaky abstractions and it's >>> > unclear what that custom __init__ should look like. How am I supposed >>> to >>> > know what the replacement for default_factory is? >>> > >>> > Moreover, suppose I want one base class with an optional argument and a >>> > half dozen subclasses each with their own required argument. At that >>> > point, I have to write the same __init__ function a half dozen times. >>> > >>> > It feels rather burdensome for the user when an additional flag (say >>> > "kw_only=True") and a modification to: >>> > https://github.com/python/cpython/blob/master/Lib/dataclasses.py#L294 >>> that >>> > inserted `['*']` after `[self_name]` if the flag is specified could >>> > ameliorate this entire issue. >>> > >>> > On Wed, Jan 24, 2018 at 3:22 PM Ivan Levkivskyi <levkivs...@gmail.com >>> > <mailto:levkivs...@gmail.com>> wrote: >>> > >>> > It is possible to pass init=False to the decorator on the subclass >>> > (and supply your own custom __init__, if necessary): >>> > >>> > @dataclass >>> > class Foo: >>> > some_default: dict = field(default_factory=dict) >>> > >>> > @dataclass(init=False) # This works >>> > class Bar(Foo): >>> > other_field: int >>> > >>> > -- >>> > Ivan >>> > >>> > >>> > >>> > On 23 January 2018 at 03:33, George Leslie-Waksman >>> > <waks...@gmail.com <mailto:waks...@gmail.com>> wrote: >>> > >>> > The proposed implementation of dataclasses prevents defining >>> > fields with defaults before fields without defaults. This can >>> > create limitations on logical grouping of fields and on >>> inheritance. >>> > >>> > Take, for example, the case: >>> > >>> > @dataclass >>> > class Foo: >>> > some_default: dict = field(default_factory=dict) >>> > >>> > @dataclass >>> > class Bar(Foo): >>> > other_field: int >>> > >>> > this results in the error: >>> > >>> > 5 @dataclass >>> > ----> 6 class Bar(Foo): >>> > 7 other_field: int >>> > 8 >>> > >>> > >>> >>> ~/.pyenv/versions/3.6.2/envs/clover_pipeline/lib/python3.6/site-packages/dataclasses.py >>> > in dataclass(_cls, init, repr, eq, order, hash, frozen) >>> > 751 >>> > 752 # We're called as @dataclass, with a class. >>> > --> 753 return wrap(_cls) >>> > 754 >>> > 755 >>> > >>> > >>> >>> ~/.pyenv/versions/3.6.2/envs/clover_pipeline/lib/python3.6/site-packages/dataclasses.py >>> > in wrap(cls) >>> > 743 >>> > 744 def wrap(cls): >>> > --> 745 return _process_class(cls, repr, eq, order, >>> > hash, init, frozen) >>> > 746 >>> > 747 # See if we're being called as @dataclass or >>> > @dataclass(). >>> > >>> > >>> >>> ~/.pyenv/versions/3.6.2/envs/clover_pipeline/lib/python3.6/site-packages/dataclasses.py >>> > in _process_class(cls, repr, eq, order, hash, init, frozen) >>> > 675 # in __init__. Use >>> > "self" if possible. >>> > 676 '__dataclass_self__' >>> if >>> > 'self' in fields >>> > --> 677 else 'self', >>> > 678 )) >>> > 679 if repr: >>> > >>> > >>> >>> ~/.pyenv/versions/3.6.2/envs/clover_pipeline/lib/python3.6/site-packages/dataclasses.py >>> > in _init_fn(fields, frozen, has_post_init, self_name) >>> > 422 seen_default = True >>> > 423 elif seen_default: >>> > --> 424 raise TypeError(f'non-default argument >>> > {f.name <http://f.name>!r} ' >>> > 425 'follows default >>> argument') >>> > 426 >>> > >>> > TypeError: non-default argument 'other_field' follows default >>> > argument >>> > >>> > I understand that this is a limitation of positional arguments >>> > because the effective __init__ signature is: >>> > >>> > def __init__(self, some_default: dict = <something>, >>> > other_field: int): >>> > >>> > However, keyword only arguments allow an entirely reasonable >>> > solution to this problem: >>> > >>> > def __init__(self, *, some_default: dict = <something>, >>> > other_field: int): >>> > >>> > And have the added benefit of making the fields in the __init__ >>> > call entirely explicit. >>> > >>> > So, I propose the addition of a keyword_only flag to the >>> > @dataclass decorator that renders the __init__ method using >>> > keyword only arguments: >>> > >>> > @dataclass(keyword_only=True) >>> > class Bar(Foo): >>> > other_field: int >>> > >>> > --George Leslie-Waksman >>> > >>> > _______________________________________________ >>> > Python-ideas mailing list >>> > Python-ideas@python.org <mailto:Python-ideas@python.org> >>> > https://mail.python.org/mailman/listinfo/python-ideas >>> > Code of Conduct: http://python.org/psf/codeofconduct/ >>> > >>> > >>> > >>> > >>> > _______________________________________________ >>> > Python-ideas mailing list >>> > Python-ideas@python.org >>> > https://mail.python.org/mailman/listinfo/python-ideas >>> > Code of Conduct: http://python.org/psf/codeofconduct/ >>> > >>> >>> >> _______________________________________________ >> Python-ideas mailing list >> Python-ideas@python.org >> https://mail.python.org/mailman/listinfo/python-ideas >> Code of Conduct: http://python.org/psf/codeofconduct/ >> >> > > > -- > --Guido van Rossum (python.org/~guido) >
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/