Dataclass attribute missing when using explicit __init__ constructor and default_factory parameter in dataclasses.field

Viewed 726

The following code creates a dataclass Obj with an int field n with default value 0.

from dataclasses import dataclass, field

@dataclass
class Obj:
    n: int = field(default_factory=int)
    
a = Obj()
print(a.n)
a.n = 0

Now, add an explicit __init__ constructor:

@dataclass
class Obj:
    n: int = field(default_factory=int)
        
    def __init__(self): # explicit constructor
        pass

It now generates this error claiming that the Obj object has no attribute named n:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [6], in <module>
      8         pass
     10 a = Obj()
---> 11 print(f'a.n = {a.n}')

AttributeError: 'Obj' object has no attribute 'n'

I thought maybe the explicit __init__ would override whatever field() is doing, but if we change from parameter default_factory to default, it works again:

@dataclass
class Obj:
    n: int = field(default=3)
        
    def __init__(self):
        pass
a.n = 3

This behavior appears in both Python 3.8 and 3.10.

2 Answers

field doesn't really "do" anything; it just provides information that the dataclass decorator uses to define an __init__ that creates and initializes the n attribute. When you define your own __init__ method instead, it's your responsibility to make sure the field is initialized according to the definition provided by field. (The same goes for the other methods that dataclass would define.)

As I told in comments, the default meaning of dataclasses is to generate special methods just by using decorators. Python docs say

This module provides a decorator and functions for automatically adding generated special methods such as __init__() and __repr__() to user-defined classes.

So as far as you are overriding __init__ method again, you will get an AttributeError

Instead try using very popular Pydantic library, as it serves same functions and features that dataclasses do and serves a lot more powerful things such as validation and custom fields (EmailStr and so on), json parsing and others

Edit

The example of similar pydantic model

from pydantic import BaseModel, Field


class Obj(BaseModel):
    n: int = Field(default_factory=int)

    def __init__(self):
        super(Obj, self).__init__()
        ...


m1 = Obj()

print(m1.n)  # 0

and with default value

from pydantic import BaseModel, Field


class Obj(BaseModel):
    n: int = Field(5)

    def __init__(self):
        super(Obj, self).__init__()
        ...


m1 = Obj()

print(m1.n)  # 5

Although it might seem kind of similar, pydantic's Field provide much more kwargs for describing the value:

def Field(
    default: Any = Undefined,
    *,
    default_factory: Optional[NoArgAnyCallable] = None,
    alias: str = None,
    title: str = None,
    description: str = None,
    exclude: Union['AbstractSetIntStr', 'MappingIntStrAny', Any] = None,
    include: Union['AbstractSetIntStr', 'MappingIntStrAny', Any] = None,
    const: bool = None,
    gt: float = None,
    ge: float = None,
    lt: float = None,
    le: float = None,
    multiple_of: float = None,
    max_digits: int = None,
    decimal_places: int = None,
    min_items: int = None,
    max_items: int = None,
    unique_items: bool = None,
    min_length: int = None,
    max_length: int = None,
    allow_mutation: bool = True,
    regex: str = None,
    discriminator: str = None,
    repr: bool = True,
    **extra: Any,
) -> Any:
Related