dataclasses: InitVar Defaults Leave a Stale Class Attribute (tested)
dataclasses.InitVar marks a field as init-only: it's accepted by the generated __init__ and handed to __post_init__, but it's documented as not becoming a real instance attribute. We measured what happens when code -- a method added later, a subclass, a debugger -- accesses that field by name anyway, and whether giving it a default value changes the answer.
1. An InitVar with no default fails loudly on access, as expected
from dataclasses import dataclass, InitVar
@dataclass
class Config:
name: str
multiplier: InitVar[int]
value: int = 0
def __post_init__(self, multiplier):
self.value = self.value * multiplier if self.value else multiplier
c = Config("x", 7, value=3)
print(c.value)
print(c.multiplier)
21
Traceback (most recent call last):
...
AttributeError: 'Config' object has no attribute 'multiplier'
Measured: __post_init__ correctly receives multiplier=7 and computes value. Accessing c.multiplier afterward raises AttributeError -- exactly as documented, since InitVar fields are never stored on the instance. Code that tries to reach for the field by name fails immediately and visibly.
2. The identical field with a default value returns the wrong value instead of raising
from dataclasses import dataclass, InitVar
@dataclass
class Config:
name: str
multiplier: InitVar[int] = 1
value: int = 0
def __post_init__(self, multiplier):
self.value = self.value * multiplier if self.value else multiplier
c1 = Config("a", multiplier=5, value=3)
c2 = Config("b", multiplier=9, value=2)
print(c1.value, c2.value)
print(c1.multiplier, c2.multiplier)
15 18
1 1
# Config.multiplier (class attribute) == 1
Measured: both instances compute the correct value from their own multiplier argument (5 and 9) inside __post_init__. But c1.multiplier and c2.multiplier both read back as 1 -- neither the value actually passed in. Giving an InitVar a default value makes dataclasses set that default as a class attribute (the same mechanism used for normal field defaults), and since no instance attribute of that name ever gets created, attribute lookup silently falls through to the stale class-level default instead of raising. The field looks readable and present; the value it returns has no relationship to what the instance was constructed with.
3. The fix: don't rely on reading an InitVar back -- store what you need in a real field
from dataclasses import dataclass, field, InitVar
@dataclass
class Config:
name: str
multiplier: InitVar[int] = 1
value: int = 0
multiplier_used: int = field(default=0, repr=False)
def __post_init__(self, multiplier):
self.value = self.value * multiplier if self.value else multiplier
self.multiplier_used = multiplier
c = Config("a", multiplier=5, value=3)
print(c.value, c.multiplier_used)
15 5
Measured: assigning the InitVar's value to a normal field inside __post_init__ makes it a real, correct instance attribute. If any code might need to read the value back later -- including a future maintainer who doesn't know multiplier is init-only -- an InitVar with a default is a trap precisely because the wrong read succeeds instead of failing.
Frequently asked questions
Does accessing a dataclasses.InitVar field after construction raise an error?
It depends on whether the InitVar has a default. We measured an InitVar with no default raising AttributeError on access, as documented. But an InitVar with a default value instead returns that default silently on every instance, regardless of what was actually passed to __init__.
Why does my dataclasses.InitVar field return the wrong value?
Because giving an InitVar a default causes dataclasses to set that default as a class attribute, the same mechanism used for ordinary field defaults. Since the InitVar never becomes an instance attribute, attribute lookup falls through to that stale class-level default -- we confirmed this returns the literal default from the class body, not the value passed to any specific instance.
Is it safe to read a dataclasses.InitVar field back after __post_init__ runs?
No -- we measured that InitVar fields never become instance attributes regardless of default. If you need the init-time value later, assign it to a real field inside __post_init__ instead of relying on the InitVar name.
Does an InitVar with no default behave differently from one with a default?
Yes, measurably -- we confirmed an InitVar with no default raises AttributeError on access (fails loudly), while one with a default returns that default value with no error at all (fails silently, with wrong data), even though both are equally unavailable as real instance state.
What's the safe way to keep an InitVar's value accessible on a dataclass instance?
Copy it into a real field inside __post_init__, e.g. self.multiplier_used = multiplier. We measured this producing a correct, instance-specific value, unlike reading the InitVar name directly.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in dataclasses module: declared an InitVar field with a default value, constructed instances passing different values for it, then accessed the field by name on each instance and compared against a second class using an InitVar with no default. PlainHeaders is our extension; Python's dataclasses module is built in.