Allow for positional- or keyword-only arguments in dataclasses

There are some data classes where I want to have only keyword arguments, and some where I want only positional. Take for example:

@dataclass()
class XYZ:
    x: int
    y: int
    z: int

# For this dataclass, it might make sense to force positional-only such that
# this works:
XYZ(1, 2, 3)

# But these don't:
XYZ(1, 2, z=3)
XYZ(x=1, y=2, z=3)

Similarly, and more importantly, one might want to force keyword arguments:

@dataclass()
class Credentials:
    username: str
    password: str

# With this, we should allow:
Credentials(username="mikeholler", password="hunter2")

# But maybe not allow for concern of misuse and confusion:
Credentials("mikeholler", "hunter2")
Credentials("mikeholler", password="hunter2")

After reading dataclasses.py from the 3.8 sources, it seems like there’s no way to get at the constructor parameters to write special handling logic for myself (without using init=False and writing a custom init function every time, which is an unacceptable solution in my opinion). This means the solution would have to lie within an update to dataclass itself, some syntax to say “here is where forced positional arguments and” and “here is where forced kwargs end”.

This could be, perhaps, added to the field(...) function, but implementation details aside, the ability to specify which args in a dataclass’s constructor should be positional and which should be kwargs (or both when both are allowed) would be a great quality of life improvement.

Does anybody have good ideas for a syntax that would be best for this? Anyone also think this is a good idea?

A note: one thing I also tried was providing my own __init__ function when @dataclass(init=True), hoping the generated init class might do it’s thing and then call mine. But, alas, it seems like it only replaces it without error (so mine is not called at all).

Might be nice to have @dataclass generate a warning or error if it tries to set the __init__ function when one already exists to avoid confusion (probably the same for __repr__, etc.)

I found a workaround to allow this using a decorator. The problem is, the decorator breaks IDE support for parameters, but otherwise works as expected. See the gist here:

I still think something like this would be nice in stdlib.

Hi Michael,

you write:

one thing I also tried was providing my own __init__ function when @dataclass(init=True) , hoping the generated init class might do it’s thing and then call mine. But, alas, it seems like it only replaces it without error (so mine is not called at all).

Could this code perhaps help you in getting your own init method called? Let me know:

import dataclasses
@dataclasses.dataclass
class Data:
    number: int = 42
    def __post_init__(self):
        print('Hello, world!')
print(Data().number)

outputs:

Hello, world!
42

I found this section of the PEP maybe applicable:

Regards,

Marco