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 <eric@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-leave@python.org


_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@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/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-leave@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/