That's a really good question. I think that `__match_args__` should *only* contain the non-kw-only args, so that you would have to write `case C(a, c, b=b, d=d)` to match on all four attributes (but typically you'd probably just write `case C(a, c)` -- that's about the same as `case C(a, c, b=_, d=_)` except it doesn't even care whether b and d are set at all.
On Tue, Mar 16, 2021 at 1:36 PM Eric V. Smith <e...@trueblade.com> wrote: > And now I have a question for you, Guido. > > I'm looking at the code and I see the additions for __match_args__. Is > there any bad interaction between this proposal and the match statement? I > assume __match_args__ be the re-ordered arguments to __init__, but I want > to make sure. > > So this: > @dataclasses.dataclass > class C: > a: Any > b: Any = field(kw_only=True) > c: Any > d: Any = field(kw_only=True) > > Which generates: > > def __init__(self, a, c, *, b, d): > Would have __match_args__ equal to ('a', 'c', 'b', 'd'), right? Even > though the repr would have fields in order a, b, c, d. > > Eric > On 3/15/2021 7:45 PM, Guido van Rossum wrote: > > Good proposal! I have a few questions. > > On Mon, Mar 15, 2021 at 2:22 PM Eric V. Smith <e...@trueblade.com> wrote: > >> [I'm sort of loose with the terms field, parameter, and argument here. >> Forgive me: I think it's still understandable. Also I'm not specifying >> types here, I'm using Any everywhere. Use your imagination and >> substitute real types if it helps you.] >> >> Here's version 2 of my proposal: >> >> There have been many requests to add keyword-only fields to dataclasses. >> These fields would result in __init__ parameters that are keyword-only. >> >> In a previous proposal, I suggested also including positional arguments >> for dataclasses. That proposal is at >> >> https://mail.python.org/archives/list/python-ideas@python.org/message/I3RKK4VINZUBCGF2TBJN6HTDV3PVUEUQ/ >> . After some discussion, I think it's clear that positional arguments >> aren't going to work well with dataclasses. The deal breaker for me is >> that the generated repr would either not work with eval(), or it would >> contain fields without names (since they're positional). There are >> additional concerns mentioned in that thread. Accordingly, I'm going to >> drop positional arguments from this proposal. >> >> Basically, I want to add a flag to each field, stating whether the field >> results in a normal parameter or a keyword-only parameter to __init__. >> Then when I'm generating __init__, I'll examine those flags and put the >> normal arguments first, followed by the keyword-only ones. >> >> The trick becomes: how do you specify what type of parameter each field >> represents? >> >> >> What attrs does >> --------------- >> >> First, here's what attrs does. There's a parameter to their attr.ib() >> function (the moral equivalent of dataclasses.field()) named kw_only, >> which if set, marks the field as being keyword-only. From >> https://www.attrs.org/en/stable/examples.html#keyword-only-attributes : >> >> >>> @attr.s >> ... class A: >> ... a = attr.ib(kw_only=True) >> >>> A() >> Traceback (most recent call last): >> ... >> TypeError: A() missing 1 required keyword-only argument: 'a' >> >>> A(a=1) >> A(a=1) >> >> There's also a parameter to attr.s (the equivalent of >> dataclasses.dataclass), also named kw_only, which if true marks every >> field as being keyword-only: >> >> >>> @attr.s(kw_only=True) >> ... class A: >> ... a = attr.ib() >> ... b = attr.ib() >> >>> A(1, 2) >> Traceback (most recent call last): >> ... >> TypeError: __init__() takes 1 positional argument but 3 were given >> >>> A(a=1, b=2) >> A(a=1, b=2) >> >> >> dataclasses proposal >> -------------------- >> >> I propose to adopt both of these methods (dataclass(kw_ony=True) and >> field(kw_only=True) in dataclasses. The above example would become: >> >> >>> @dataclasses.dataclass >> ... class A: >> ... a: Any = field(kw_only=True) >> >> >>> @dataclasses.dataclass(kw_only=True) >> ... class A: >> ... a: Any >> ... b: Any >> >> But, I'd also like to make this a little easier to use, especially in >> the case where you're defining a dataclass that has some normal fields >> and some keyword-only fields. Using the attrs approach, you'd need to >> declare the keyword-only fields using the "=field(kw_only=True)" syntax, >> which I think is needlessly verbose, especially when you have many >> keyword-only fields. >> >> The problem is that if you have 1 normal parameter and 10 keyword-only >> ones, you'd be forced to say: >> >> @dataclasses.dataclass >> class LotsOfFields: >> a: Any >> b: Any = field(kw_only=True, default=0) >> c: Any = field(kw_only=True, default='foo') >> d: Any = field(kw_only=True) >> e: Any = field(kw_only=True, default=0.0) >> f: Any = field(kw_only=True) >> g: Any = field(kw_only=True, default=()) >> h: Any = field(kw_only=True, default='bar') >> i: Any = field(kw_only=True, default=3+4j) >> j: Any = field(kw_only=True, default=10) >> k: Any = field(kw_only=True) >> >> That's way too verbose for me. >> >> Ideally, I'd like something like this example: >> >> @dataclasses.dataclass >> class A: >> a: Any >> # pragma: KW_ONLY >> b: Any >> >> And then b would become a keyword-only field, while a is a normal field. >> But we need some way of telling dataclasses.dataclass what's going on, >> since obviously pragmas are out. >> >> I propose the following. I'll add a singleton to the dataclasses module: >> KW_ONLY. When scanning the __attribute__'s that define the fields, a >> field with this type would be ignored, except for assigning the kw_only >> flag to fields declared after these singletons are used. So you'd get: >> >> @dataclasses.dataclass >> class B: >> a: Any >> _: dataclasses.KW_ONLY >> b: Any >> >> This would generate: >> >> def __init__(self, a, *, b): >> >> This example is equivalent to: >> >> @dataclasses.dataclass >> class B: >> a: Any >> b: Any = field(kw_only=True) >> >> The name of the KW_ONLY field doesn't matter, since it's discarded. I >> think _ is a fine name, and '_: dataclasses.KW_ONLY' would be the >> pythonic way of saying "the following fields are keyword-only". >> >> My example above would become: >> >> @dataclasses.dataclass >> class LotsOfFields: >> a: Any >> _: dataclasses.KW_ONLY >> b: Any = 0 >> c: Any = 'foo' >> d: Any >> e: Any = 0.0 >> f: Any >> g: Any = () >> h: Any = 'bar' >> i: Any = 3+4j >> j: Any = 10 >> k: Any >> >> Which I think is a lot clearer. >> >> The generated __init__ would look like: >> >> def __init__(self, a, *, b=0, c='foo', d, e=0.0, f, g=(), h='bar', >> i=3+4j, j=10, k): >> >> The idea is that all normal argument fields would appear first in the >> class definition, then all keyword argument fields. This is the same >> requirement as in a function definition. There would be no switching >> back and forth between the two types of fields: once you use KW_ONLY, >> all subsequent fields are keyword-only. A field of type KW_ONLY can >> appear only once in a particular dataclass (but see the discussion below >> about inheritance). >> >> >> Re-ordering args in __init__ >> ---------------------------- >> >> If, using field(kw_only=True), you specify keyword-only fields before >> non-keyword-only fields, all of the keyword-only fields will be moved to >> the end of the __init__ argument list. Within the list of >> non-keyword-only arguments, all arguments will keep the same relative >> order as in the class definition. Ditto for within keyword-only arguments. >> >> So: >> >> @dataclasses.dataclass >> class C: >> a: Any >> b: Any = field(kw_only=True) >> c: Any >> d: Any = field(kw_only=True) >> >> Then the generated __init__ will look like: >> >> def __init__(self, a, c, *, b, d): >> >> __init__ is the only place where this rearranging will take place. >> Everywhere else, and importantly in __repr__ and any dunder comparison >> methods, the order will be the same as it is now: in field declaration >> order. >> > > Can you be specific and show what the repr() would be? E.g. if I create > C(1, 2, b=3, d=4) the repr() be C(a=1, b=3, c=2, d=4), right? > > >> This is the same behavior that attrs uses. >> > > Nevertheless I made several typos trying to make the examples in my > sentence above correct. Perhaps we could instead disallow mixing kw-only > and regular args? Do you know why attrs does it this way? > > >> Inheritance >> ----------- >> >> There are a few additional quirks involving inheritance, but the >> behavior would follow naturally from how dataclasses already handles >> fields via inheritance and the __init__ argument re-ordering discussed >> above. Basically, all fields in a derived class are computed like they >> are today. Then any __init__ argument re-ordering will take place, as >> discussed above. >> >> Consider: >> >> @dataclasses.dataclass(kw_only=True) >> class D: >> a: Any >> >> @dataclasses.dataclass >> class E(D): >> b: Any >> >> @dataclasses.dataclass(kw_only=True) >> class F(E): >> c: Any >> >> This will result in the __init__ signature of: >> >> def __init__(self, b, *, a, c): >> >> However, the repr() will still produce the fields in order a, b, c. >> Comparisons will also use the same order. >> > > This can be simulated by flattening the inheritance tree and adding > explicit field(kw_only=True) to all fields of classes using kw_only=True in > the class decorator as well as all fields affected by _: KW_ONLY, right? So > the above would behave like this: > > @dataclasses.dataclass > class F: > a: Any = field(kw_only=True) > b: Any > c: Any = field(kw_only=True) > > which IIUC indeed gives the same __init__ signature and repr(). > > >> Conclusion >> ---------- >> >> Remember, the only point of all of these hoops is to add a flag to each >> field saying what type of __init__ argument it becomes: normal or >> keyword-only. Any of the 3 methods discussed above (kw_only flag to >> @dataclass(), kw_only flag to field(), or the KW_ONLY marker) all have >> the same result: setting the kw_only flag on one or more fields. >> >> The value of that flag, on a per-field basis, is used to re-order >> __init__ arguments, and is used in generating the __init__ signature. >> It's not used anywhere else. >> >> I expect the two most common use cases to be the kw_only flag to >> @dataclass() and the KW_ONLY marker. I would expect the usage of the >> kw_only flag on field() to be rare, but since it's the underlying >> mechanism and it's needed for more complex field layouts, it is included >> in this proposal. >> >> So, what do you think? Is this a horrible idea? Should it be a PEP, or >> just a 'simple' feature addition to dataclasses? I'm worried that if I >> have to do a full blown PEP I won't get to this for 3.10. >> > > I don't think it is very controversial, do you? Then again maybe you > should ask a SC member if they would object. > > mypy and other type checkers would need to be taught about all of this. >> > > Yeah, that's true. But the type checkers have bigger fish to fry (e.g. > pattern matching). > > -- > --Guido van Rossum (python.org/~guido) > *Pronouns: he/him **(why is my pronoun here?)* > <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/> > > _______________________________________________ > Python-ideas mailing list -- python-ideas@python.org > To unsubscribe send an email to > python-ideas-leave@python.orghttps://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at > https://mail.python.org/archives/list/python-ideas@python.org/message/AZN66ZACAH6BGX2OGDQI7GV3X6SETRUP/ > Code of Conduct: http://python.org/psf/codeofconduct/ > > -- > Eric V. Smith > > -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/SMNBFYZO6VIC6YYINGXU5ZS5EPDF5QOF/ Code of Conduct: http://python.org/psf/codeofconduct/