1
from dataclasses import field, dataclass

custom_field = field()

@dataclass
class A:
    x: int
    y: int = custom_field
    z: int = custom_field

A(x=1, y=2, z=3)

After executing the following code I get this error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-1314913987c2> in <module>
----> 1 A(x=1, y=2, z=3)

TypeError: __init__() got an unexpected keyword argument 'y'

I can overcome this issue by using the following code:

from dataclasses import field, dataclass

custom_field = lambda: field()

@dataclass
class A:
    x: int
    y: int = custom_field()
    z: int = custom_field()

A(x=1, y=2, z=3)

Tested with python 3.7.9

  • 3
    By calling `field()` eagerly like you are doing in `custom_field = field()` you are creating only one `Field` object - which is bound to both `y` & `z`. I suspect that is causing dataclasses to misbehave - the created class only has a `z`, no `y`. Instead you should use it how it's documented. You might find the `default_factory` argument to `field` useful. – rdas Nov 11 '20 at 13:39
  • Try to `print(*dataclasses.fields(A), sep='\n')` in both cases and see the differences – Tomerikoo Nov 11 '20 at 13:54
  • It's arguably a bug, as it's possible to detect if an instance of `Field` is being rebound to a second class attribute. I don't know if there is a subtle reason to *allow* that, or if you could safely raise an error if someone attempts to do so. – chepner Nov 11 '20 at 14:05

1 Answers1

1

field creates and returns a single instance of dataclasses.Field.

When you do custom_field = field() - you simply assign that one Field to custom_field. Then when you do y = custom_field, and z = custom_field, you are assigning the same object to different names. What ends up happening is that since they are both references to the same field, only the one assigned last, z, is kept.

Which is why if you do

A(x=1, z=3)

it works just fine But if you do-

A(x=1, y=2, z=3)

It raises an exception since y doesn't actually exist in the class.

chepner
  • 497,756
  • 71
  • 530
  • 681
Chase
  • 5,315
  • 2
  • 15
  • 41
  • 1
    If you look at the source code, `Field.__set_name__` is used to attach *a* name to each instance of `Field`; this is the point where the dataclass "forgets" about the name `y` if you assign the same object to the name `z`. – chepner Nov 11 '20 at 14:03
  • 1
    Arguably, this *could* be considered a bug, as `__set_name__` could detect if the same object is being assigned a second time and raise some appropriate error. – chepner Nov 11 '20 at 14:04
  • @chepner hmm I see. Also yes, raising an exception when multiple names are being assigned to the same reference would probably be a good idea. – Chase Nov 11 '20 at 14:05