Add `alias` as a `field()` parameter for dataclasses

Since attrs 22.2.0, attrs.field has an alias parameter that changes how a field is called in the synthesized __init__:

from attrs import define, field

@define
class C:
    _name: tuple[str, str] = field(alias="name")

    @property
    def full_name(self) -> str:
        return " ".join(self._name)

c = C(name=("John", "Smith"))  # `name` not `_name`
print(c.full_name)

The typical use-case for this is private instance variables as in the above example. But there could be other use-cases as well, like

@define
class C:
    viewport: tuple[int, int] = field(alias="vp")

C(vp=(2, 4))  # constructor uses shorthand

Besides being a useful feature in and of itself, another reason to add this to the standard library is that it would bring dataclasses in line with dataclass_transform which already supports the alias parameter in field(): PEP 681 – Data Class Transforms | peps.python.org . It is currently a bit strange that dataclass_transform has a feature that dataclasses.dataclass doesn’t actually have.

3 Likes

I’m supportive of this. Can you open an issue and assign it to me? I have done a little work on this, but I want to review how attrs handles it before I finish it.

1 Like

I created an issue: Add `alias` as a `field()` parameter for dataclasses · Issue #101192 · python/cpython · GitHub but I don’t think I can assign anyone.

1 Like

Thanks! I assigned it to me. I’m hoping some typing folks can take a look once I have a PR.

Given that this only affects __init__, why not just reuse the init parameter? If the argument is a string, use it as the alias.

class A:
    b = field(init="c")

a = A(c=42)
print(a.b)  # 42

To make this work, you would have to get adoption from type checks (eg mypy) and IDEs (eg Spyder, VSCode, PyCharm). That shouldn’t be too hard if they already support attrs.


In general, I’m against this practice. I believe developers should have full and supported access to the instantiation arguments.

That’s not a bad idea… but attrs and typing.dataclass_transform already use “alias” for this, so I think it’s maybe too late to change this.

2 Likes

Is there a reason why the alias shouldn’t also change the repr if it’s changing the parameter name passed to __init__?

It does bug me (with attrs) that if you use aliases you end up with this:

>>> from attrs import define, field
>>> @define
... class A:
...     x = field(alias='y')
... 
>>> A(y=2)
A(x=2)
2 Likes

It seems dataclasses have the same issue when using InitVar and init=False:

>>> from dataclasses import dataclass, field, InitVar
>>> @dataclass
... class C:
...   x: InitVar[int]
...   _x: int = field(init=False)
...   
...   def __post_init__(self, x: int):
...     self._x = x
... 
>>> C(x=1)
C(_x=1)

If you want to see the alias in the repr, then for consistency, any InitVars should probably also be shown in the repr and init=False fields should not be.

(I personally don’t really have an opinion on this either way; I rarely look at the repr.)

Perhaps the attrs authors foresaw pattern matching:

In [1]: from attrs import define, field

In [2]: @define
   ...: class A:
   ...:     x = field(alias='y')
   ...:

In [3]: A(y=2)
Out[3]: A(x=2)

In [4]: match A(y=2):
    ...:     case A(y=2):
    ...:         print('matched y')
    ...:     case A(x=2):
    ...:         print('matched x')
    ...:
matched x
1 Like

That outcome from pattern matching thing seems evil somehow.

I see now that the repr not being a valid call while looking like one was already an existing condition with any of these parameters:

>>> @dataclass
... class C:
...     x: int = field(init=False, default=1)
...     y: int = field(repr=False, default=2)
...     z: InitVar[int] = 3
...     
>>> C(y=10, z=20)
C(x=1)

I guess I’d just expect the repr to not look like a valid call if it’s clearly not going to be one? This actually lead me to change the repr under these conditions in my own implementation.