In response to Eric V. Smith, if something like what you're suggesting were to be implemented I would much rather it be done with context managers than special values, because otherwise you once again end up in a situation where it's impossible to easily subclass a dataclass (which was one of the primary reasons this conversation even got started in the first place). So, for example:
import dataclasses @dataclasses.dataclass class SomeClass: c: bool = False # a normal field with a default value does not # prevent subsequent positional fields from # having no default value (such as 'a' below) # however, all further normal fields now must # specify a default value (such as 'd' below) with dataclasses.positional(): a: int b: float = 3.14 # once a positional field with a default value shows up # all further positional fields and ALL normal fields # (even retroactively!) must also specify defaults # (for example, field 'c' above is # now forced to specify a default value) with dataclasses.keyword(): e: list f: set = dataclasses.field(default_factory=set) # once a keyword field with a default value shows up # all further keyword fields must also specify defaults d: dict = dataclasses.field(default_factory=dict) # this will generate an __init__ like: def __init__(self, a: int, b: float = 3.14, /, c: bool = False, d: dict = None, *, e: list, f: set = None): self.a = a self.b = b self.c = c self.d = dict() if d is None else d self.e = e self.f = set() if f is None else f # parameters are arranged in order as # positional -> normal -> keyword # within the order they were defined in each # individual category, but not necessarily # whatever order they were defined in overall # # This is subclass-friendly! # # it should hopefully be obvious that we could # have cut this class in half at literally any # point (as long as the the parent class has # the earlier arguments within each category) # and put the rest into a child class and # it would still have worked and generated the # same __init__ signature # # For example: @dataclasses.dataclass class Parent: c: bool = False with dataclasses.keyword(): e: list with dataclasses.positional(): a: int @dataclasses.dataclass class Child(Parent): with dataclasses.keyword(): f: set = dataclasses.field(default_factory=set) d: dict = dataclasses.field(default_factory=dict) with dataclasses.positional(): b: float = 3.14 # we have shuffled around the ordering of the # context managers and normal fields in both # classes and it STILL works unambiguously! Honestly, the more I think about it the more I'm +1 on something like this (even if it's not *exactly* my suggestion). Right now dataclasses do not support the full range of __init__ signatures you could generate with a normal class (and are extremely hostile to subclassing), and that is a failing that often forces people to fall back to normal classes in otherwise ideal dataclass use-case situations. On Thu, Mar 11, 2021 at 7:35 AM Eric V. Smith <e...@trueblade.com> wrote: > On 3/11/2021 1:41 AM, Paul Bryan wrote: > > In my experience, a dataclass with more than a few attributes makes using > positional arguments unwieldy and error-prone. > > Agreed, just like any function or class. > > I would think something like @dataclass(kwonly=*bool*) with default of > False would be reasonably clean to implement and understand. > > Yes, I think that's a reasonable thing to do. But I don't want it to be > the only option, I'd like to be able to mix and match some "normal" > arguments and some keyword-only (and some positional-only). > > > While I appreciate supporting existing behavior for backward > compatibility, I'm not so clear on the value of supporting a hybrid of > positional and keyword __init__ arguments. Could you shed some light on > your reasoning for supporting it? > > The same as any function or class. From PEP 3102: > > def compare(a, b, *, key=None): > > This seems like a reasonable thing to want a dataclass to represent. Using > my off-the-cuff proposal from below: > > @dataclasses.dataclass > class Comparator: > a: Any > b: Any > _: dataclasses.KEYWORD_ONLY > key: Optional[Callable[whatever]] = None > > I don't want to restrict dataclasses: I'd like the full range of argument > types to be available. This is especially true as dataclasses are used for > more and more things (at least that's what happens in my code). > > Eric > > > > On Thu, 2021-03-11 at 00:47 -0500, Eric V. Smith wrote: > > As I've said before, I'm not opposed to the feature of keyword-only > arguments. I think it would be a great addition. > > However, the proposal from Laurie O is only for changing fields without > default values following fields with default values to be keyword-only. At > least that's how I understand it. > > So, that means that: > > @dataclasses.dataclass > class Point: > x: int = 0 > y: int > z: int > t: int = 0 > > Would generate a __init__ with this signature: > > def __init__(self, x=0, *, y, z, t=0): > > While it's an interesting application, I think that's just too limiting. > Among other things, I can't define a dataclass where all fields are > keyword-only, or a class where there's only a single field and it's > keyword-only. I also have to have at least one keyword-only field (here, y) > that has no default value. z and t can have defaults, but not y. > > What I'd like to see is some way of having keyword-only arguments, with or > without defaults. And I'd also like to see if we could get support for > positional-only arguments at the same time. > > I'm not sure of the best way to achieve this. Using flags to field() > doesn't sound awesome, but could be made to work. Or maybe special field > names or types? I'm not crazy about that, but using special types would let > you do something like: > > @dataclasses.dataclass > class Point: > x: int = 0 > _: dataclasses.KEYWORD_ONLY > y: int > z: int > t: int = 0 > > And the dataclasses machinery would ignore the "_" field except for making > everything after it keyword-only. Here the name "_" isn't special: any > field (of any name) that's of type dataclasses.KEYWORD_ONLY would be > ignored except for the keyword-only changing behavior. In some ways, it's > like dataclasses.ClassVar, where the type is treated specially and the > field doesn't become a __init__ argument. > > There are also issues with inheritance that would need to be thought > through. This idea could also be extended for positional-only. > > I'm open to other suggestions. > > Eric > On 3/10/2021 10:22 AM, Guido van Rossum wrote: > > _______________________________________________ > Python-ideas mailing list -- > To unsubscribe send an email to > > Message archived at > Code of Conduct: > > -- > > Eric V. Smith > > _______________________________________________ > 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/XSAYT2MFOIBYHYINDHLPR7V2WHCWRYPE/ > Code of Conduct: http://python.org/psf/codeofconduct/ > > > > _______________________________________________ > 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/UGNLUWT4OQC2JMEXSNIJRRCC4KMBE6XJ/ > Code of Conduct: http://python.org/psf/codeofconduct/ > > -- > Eric V. Smith > > _______________________________________________ > 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/6E6AI6GOEBS6XGUI5YFEO5JQ4N6RGLNE/ > Code of Conduct: http://python.org/psf/codeofconduct/ >
_______________________________________________ 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/2M7LYIFW3E3QSOHEPZYQP2M7LJGHGMUZ/ Code of Conduct: http://python.org/psf/codeofconduct/