Skip to main content

Lib/dataclasses.py (part 2)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/dataclasses.py

This annotation covers advanced dataclass features added in 3.10-3.14. See lib_dataclasses_detail for @dataclass, __init__ generation, and field() basics.

Map

LinesSymbolRole
1-80KW_ONLY sentinelFields after KW_ONLY become keyword-only in __init__
81-200InitVarField type that appears in __init__ but not stored as attribute
201-350__post_init__Hook called at end of generated __init__
351-500__slots__ generation@dataclass(slots=True) rebuilds class with __slots__
501-650__match_args__PEP 634 positional pattern matching support
651-900field() advanceddefault_factory, compare, hash, metadata
901-1100replace()Create modified copy (like namedtuple._replace)
1101-1400make_dataclass()Programmatic dataclass creation from field spec

Reading

KW_ONLY

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

# Usage:
@dataclass
class Point:
x: float
y: float
_: KW_ONLY
label: str = '' # keyword-only from here on

The _process_class function marks all fields after KW_ONLY with kw_only=True.

InitVar

# CPython: Lib/dataclasses.py:290 InitVar
class InitVar:
"""Marks a field that appears in __init__ but is not stored."""
def __init__(self, type): self.type = type
def __class_getitem__(cls, type): return cls(type)

# Usage:
@dataclass
class C:
init_val: InitVar[int]
value: int = field(init=False)
def __post_init__(self, init_val):
self.value = init_val * 2

InitVar fields are passed to __post_init__ but not assigned as instance attributes.

__post_init__

# CPython: Lib/dataclasses.py:380 _init_fn (generates __init__)
def _init_fn(fields, frozen, has_post_init, self_name, globals, slots):
# ...generate __init__ body...
if has_post_init:
# Append: self.__dataclass_post_init__(initvar1, initvar2, ...)
body_lines.append(f' {self_name}.{POST_INIT_NAME}('
+ ','.join(f.name for f in fields
if isinstance(f.type, InitVar))
+ ')')

slots=True

# CPython: Lib/dataclasses.py:510 _add_slots
def _add_slots(cls, is_frozen, has_weakref_slot):
# Collect field names for __slots__
cls_fields = [f.name for f in fields(cls)]
# Rebuild the class with __slots__ set
# (cannot add __slots__ to an existing class)
new_cls = type(cls)(cls.__name__, cls.__bases__,
{**{k: v for k, v in cls.__dict__.items()
if k not in cls_fields},
'__slots__': tuple(cls_fields)})
new_cls.__qualname__ = cls.__qualname__
return new_cls

slots=True creates a brand-new class object, so any existing references to the original class will not see the slotted version.

replace()

# CPython: Lib/dataclasses.py:1200 replace
def replace(obj, /, **changes):
"""Return a new dataclass instance with fields replaced."""
if not _is_dataclass_instance(obj):
raise TypeError("replace() should be called on dataclass instances")
# Build init kwargs from current values + overrides
init_fields = {f.name: f for f in fields(obj) if f.init}
kwargs = {}
for f in fields(obj):
if not f.init: continue
if f.name in changes:
kwargs[f.name] = changes.pop(f.name)
else:
kwargs[f.name] = getattr(obj, f.name)
if changes:
raise TypeError(f'replace() got unexpected keyword arguments: {list(changes)}')
return obj.__class__(**kwargs)

gopy notes

dataclasses is pure Python. __slots__ generation works via type() which calls tp_new in gopy's objects/type_call.go. InitVar annotation handling uses get_type_hints() which requires typing module support.