Skip to main content

Lib/dataclasses.py (part 4)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/dataclasses.py

This annotation covers advanced dataclass features. See lib_dataclasses3_detail for @dataclass, field(), __init__ generation, and __repr__.

Map

LinesSymbolRole
1-80__post_init__Called after __init__; used for validation
81-160InitVarParameters that appear in __init__ but are not fields
161-240ClassVarClass-level annotations excluded from dataclass
241-360KW_ONLY sentinelForce all following fields to be keyword-only
361-500slots=TrueGenerate __slots__ to save memory

Reading

__post_init__

# CPython: Lib/dataclasses.py:540 _init_fn (post_init call injection)
def _init_fn(fields, frozen, has_post_init, self_name, globals):
body_lines = []
for f in fields:
... # assign each field
if has_post_init:
body_lines += [f'{self_name}.{_POST_INIT_NAME}({",".join(seen_default)})']
return _create_fn('__init__', ...)

If the class defines __post_init__, the generated __init__ calls it at the end, passing any InitVar values. This is where cross-field validation belongs: if self.end < self.start: raise ValueError(...).

InitVar

# CPython: Lib/dataclasses.py:280 InitVar
class InitVar:
__slots__ = ('type',)
def __init__(self, type):
self.type = type
def __class_getitem__(cls, type):
return cls(type)

# Usage:
@dataclass
class C:
x: int
y: InitVar[int]
def __post_init__(self, y):
self.x = self.x + y

InitVar fields appear as parameters in __init__ (and in __post_init__) but are not stored as attributes. y: InitVar[int] means __init__ accepts y, passes it to __post_init__, and then discards it.

ClassVar

# CPython: Lib/dataclasses.py:240 _is_classvar
def _is_classvar(a_type, typing):
return (a_type is typing.ClassVar
or (type(a_type) is typing._GenericAlias
and a_type.__origin__ is typing.ClassVar))

x: ClassVar[int] = 0 is excluded from __init__, __repr__, and __eq__. The dataclass machinery calls _is_classvar to filter it out. typing.get_type_hints would include it, but fields() does not.

KW_ONLY

# CPython: Lib/dataclasses.py:320 KW_ONLY sentinel
KW_ONLY = KW_ONLY()

@dataclass
class C:
x: int
_: KW_ONLY
y: int # keyword-only from here on
z: int = 0 # also keyword-only

A field annotated with type KW_ONLY acts as a marker: all fields declared after it are keyword-only in __init__. This is a sentinel instance, not a type; the dataclass metaclass detects it by identity.

slots=True

# CPython: Lib/dataclasses.py:1020 _add_slots
def _add_slots(cls, is_frozen, hasdict, weakref_slot):
# Create a new class with __slots__ set to the field names
cls_dict = {}
for key, value in cls.__dict__.items():
if key not in ('__dict__', '__weakref__'):
cls_dict[key] = value
cls_dict['__slots__'] = tuple(f.name for f in fields(cls))
qualname = cls.__qualname__
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
cls.__qualname__ = qualname
return cls

@dataclass(slots=True) creates a new class (because __slots__ must be set at class creation time, not patched afterward). The original class is discarded and a new one with __slots__ is returned.

gopy notes

__post_init__ is called at the end of module/dataclasses.genInit in module/dataclasses/module.go. InitVar fields are detected by _isInitVar and included in the __init__ signature but omitted from objects.Instance fields. KW_ONLY sets the kwonly flag on subsequent Field structs. slots=True generates a type with __slots__ that maps to Go struct fields.