What does attrs' solution for this problem look like?

On Fri, Jan 26, 2018 at 11:11 AM, George Leslie-Waksman <waksman@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?


On Thu, Jan 25, 2018 at 5:12 AM Eric V. Smith <eric@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:

     class Foo:
         some_default: dict = field(default_factory=dict)

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.


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@gmail.com
> <mailto:levkivskyi@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@gmail.com <mailto:waksman@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
Code of Conduct: http://python.org/psf/codeofconduct/

--Guido van Rossum (python.org/~guido)