Skip to main content

Lib/dataclasses.py

Source:

cpython 3.14 @ ab2d84fe1023/Lib/dataclasses.py

Map

LinesSymbolPurpose
1–80module header, __all__Imports, sentinel objects, KW_ONLY marker
81–260Field, field()Field descriptor and factory function
261–400_process_classCore decorator logic: collect fields, call generators
401–480_init_fnGenerate __init__ source, compile and exec it
481–530_repr_fnGenerate __repr__ via compile+exec
531–570__post_init__ insertionAppend call inside generated __init__ when defined
571–640_frozen_setattr, _frozen_delattrRaise FrozenInstanceError for frozen classes
641–720@dataclass decoratorEntry point, delegates to _process_class
721–820fields(), asdict(), astuple()Public introspection helpers
821–900replace()Shallow copy with field overrides
901–1000make_dataclass()Dynamic class construction
1001–1500_DataclassParams, slots, __match_args__Params container, __slots__ rewriting, pattern matching support

Reading

@dataclass decorator and _process_class

The public @dataclass decorator is a thin wrapper. When called with arguments it returns a decorator; when called directly on a class it calls _process_class immediately.

_process_class walks the class MRO to collect Field objects, then calls the individual generator functions to attach __init__, __repr__, __eq__, and optionally __lt__/__hash__. The result is the original class, mutated in place.

# CPython: Lib/dataclasses.py:1049 _process_class
def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
match_args, kw_only, slots, weakref_slot):
fields = {}
...
for name, type in fields.items():
...
if init:
flds = [f for f in fields.values() if f.init]
_set_new_attribute(cls, '__init__',
_init_fn(flds, frozen, has_post_init,
self_name, globals, slots))

The function uses _set_new_attribute so that user-defined methods are never overwritten.

Field, field(), and the compile+exec pattern

Field is not intended to be constructed directly. The field() factory validates arguments and returns a Field instance. Fields with default_factory are forbidden from also having a default.

The most distinctive implementation detail in this file is how __init__ is synthesised. Rather than building a function object via closures, _init_fn assembles Python source as a string, then calls compile() and exec() on it. This approach lets the generated function have the correct signature visible to inspect.signature() and avoids the overhead of repeated getattr calls at runtime.

# CPython: Lib/dataclasses.py:491 _init_fn
def _init_fn(fields, frozen, has_post_init, self_name, globals, slots):
...
body_lines = []
for f in fields:
...
return _create_fn('__init__',
[self_name] + [_init_param(f) for f in fields],
body_lines,
locals=locals,
globals=globals,
return_type=None)

_create_fn is the central helper that renders the source string, calls compile, execs into a fresh namespace, and returns the resulting function object. The same pattern is reused for _repr_fn, _eq_fn, and the comparison methods.

Frozen classes and replace()

When frozen=True, _process_class installs _frozen_setattr and _frozen_delattr as __setattr__ and __delattr__. These unconditionally raise FrozenInstanceError. The generated __init__ must therefore use object.__setattr__ directly to set initial values.

# CPython: Lib/dataclasses.py:601 _frozen_setattr
def _frozen_setattr(self, name, value):
# In a frozen class, disallow setting attributes.
if type(self)._is_protocol:
...
raise FrozenInstanceError('cannot assign to field ' + repr(name))

replace() creates a shallow copy by calling copy.copy() and then applying overrides with object.__setattr__ when the class is frozen, or ordinary attribute assignment otherwise. Fields marked init=False cannot be overridden via replace() and raise TypeError if the caller tries.

gopy notes

Status: not yet ported.

Planned package path: module/dataclasses/.

The compile+exec code-generation pattern has no direct equivalent in Go. The gopy port will pre-generate __init__ bytecode through the compiler pipeline rather than assembling source strings at runtime. Field metadata will map to a Go struct mirroring the CPython Field object slots. replace() depends on copy.copy(), which in turn requires the object protocol (specifically __copy__), so module/copy/ must be available before this module can pass its test suite.