pythonpython-dataclasses

Why does assigning a default to a dataclass field, make it into a class attribute?


When you add a field to a python dataclass, it will become a required argument for __init__ and it will be an instance attribute. But when you add a default value to a field it becomes a class attribute.

from dataclasses import dataclass

@dataclass
class A:
    foo: int
    bar: int = 0

>>> A.foo
AttributeError: type object 'A' has no attribute 'foo'
>>> A.bar
0

Why this difference between foo and bar?
Dataclass prevents you from setting mutable defaults, so it will not become a problem in practice, but I was curious about this behaviour, which was unexpected for me.


Solution

  • You've got things backward. Setting a field default doesn't make it a class attribute. Setting a class attribute, and annotating it, causes the @dataclass decorator to process that attribute as the default value of the field with that name.

    When you write

    @dataclass
    class A:
        foo: int
        bar: int = 0
    

    what bar: int = 0 means is that you're creating a class attribute named bar with value 0, and creating an annotation of int for that attribute.

    @dataclass will look at that and generate an __init__ that sets bar to 0 on an instance if no other value is provided, so there will be a bar instance attribute on instances you create. But the class attribute is still there too.

    There's a bit more to it than this, because if you create field objects:

    @dataclass
    class A:
        foo: int = field()
        bar: int = field(default=0)
    

    this is still setting class attributes, but @dataclass won't just leave the field objects there. A field with no default will be deleted from the class attributes, while a field with a default will be replaced with its default value. So @dataclass doesn't quite just leave all your class attributes there. It goes to deliberate effort to make sure defaults are class attributes, even in cases with field where they wouldn't be.