[Python-ideas] Dataclasses, keyword args, and inheritance
George Leslie-Waksman
waksman at gmail.com
Mon Jan 29 13:51:09 EST 2018
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 <guido at python.org> wrote:
> What does attrs' solution for this problem look like?
>
> On Fri, Jan 26, 2018 at 11:11 AM, George Leslie-Waksman <waksman at 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 <eric at 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 <levkivskyi at gmail.com
>>> > <mailto:levkivskyi at 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
>>> > <waksman at gmail.com <mailto:waksman at 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 at python.org <mailto:Python-ideas at python.org>
>>> > https://mail.python.org/mailman/listinfo/python-ideas
>>> > Code of Conduct: http://python.org/psf/codeofconduct/
>>> >
>>> >
>>> >
>>> >
>>> > _______________________________________________
>>> > Python-ideas mailing list
>>> > Python-ideas at python.org
>>> > https://mail.python.org/mailman/listinfo/python-ideas
>>> > Code of Conduct: http://python.org/psf/codeofconduct/
>>> >
>>>
>>>
>> _______________________________________________
>> Python-ideas mailing list
>> Python-ideas at python.org
>> https://mail.python.org/mailman/listinfo/python-ideas
>> Code of Conduct: http://python.org/psf/codeofconduct/
>>
>>
>
>
> --
> --Guido van Rossum (python.org/~guido)
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180129/1e569635/attachment-0001.html>
More information about the Python-ideas
mailing list