dataclasses module
Native, in-sandbox @dataclass: sandboxed code can define its own dataclasses,
executed entirely inside the sandbox (unlike host-supplied class instances,
which enter via the ClassInstance wrapper and dispatch back to the host —
see classes.md).
Host-supplied instances and this module barely interact:
dataclasses.is_dataclass(x) honours the flag the host sent, but fields()
and asdict() do not work on host instances (they are not native
dataclasses). Bare dataclasses are NOT accepted as inputs — the host must
wrap them in ClassInstance explicitly.
@dataclass, @dataclass(...) with eq and/or frozen, field() with
default/default_factory, MISSING, __post_init__ and is_dataclass
exist. Each unsupported feature listed below raises where it is written —
at the decoration, or at the field() call — rather than producing a subtly
wrong class, so a class body Monty cannot honour never silently misbehaves.
Each raises NotImplementedError, marking a feature Monty has not built yet
rather than a mistake in the calling code. CPython accepts all of them, so the
exception type is a divergence in its own right: code catching TypeError
around a decoration will not catch these.
- Every
@dataclass(...)option excepteqandfrozen—init,repr,order,unsafe_hash,match_args,kw_only,slotsandweakref_slot. Setting one away from its CPython default raisesNotImplementedError: dataclass() does not yet support the <name> option; each is named individually rather than reported as an unknown keyword. Ordering dunders therefore do not exist, and hashing is whatevereq/frozenimply. InitVar[...]— raisesNotImplementedError: dataclass() does not yet support InitVar (field <name>), which would become an ordinary field. Detected textually, since annotations are never evaluated: the name need not be imported to be rejected.- Every
field(...)argument exceptdefaultanddefault_factory—init,repr,hash,compare,metadata,kw_onlyanddoc. Setting one away from its CPython default raisesNotImplementedError: field() does not yet support the <name> argument, at thefield()call rather than at decoration. Nothing consults the three flags when the dunders are synthesized, soinit=Falsewould otherwise leave the field in__init__regardless, and Monty stores no per-field docstring fordocto fill. They therefore always read back as CPython’s defaults (f.init is True,f.kw_only is False,f.doc is None). Field.metadataandField._field_type— raiseNotImplementedError: Field.metadata is not yet supported, types.MappingProxyType is not implemented(and likewisedataclasses._FIELD), the objects behind them being unimplemented.- Module helpers —
fields,asdict,astupleandreplacedo not exist: accessing them raisesAttributeError, notNotImplementedError, since the module has no such attribute.
Mutable defaults are rejected as CPython rejects them
(ValueError: mutable default <class 'list'> for field xs is not allowed: use default_factory), and so is a non-default
field after a defaulted one
(TypeError: non-default argument 'b' follows default argument 'a').
- Annotations are stringized. Fields come from the class’s
__annotations__, which Monty stores as never-evaluated source text (always PEP 563); see typing.md. Field discovery and the generated methods are unaffected, the field type being inert metadata, butC.__dataclass_fields__['x'].typeis the string'int', not theinttype object. __dataclass_fields__holds only real fields. CPython keepsClassVar(andInitVar) entries in the mapping, marked_FIELD_CLASSVAR, and filters them infields(). Monty has no field kinds, so the mapping is the field list and class variables never appear in it.Fieldrenders differently.repr(field)follows CPython’s layout but writes the stringizedtype.repr(type(field))is<class 'Field'>, not<class 'dataclasses.Field'>(Field.__name__matches either way, so attribute errors read the same).- Error messages name
MISSING’s type with its module.MISSING | intandMISSING.foosay'dataclasses._MISSING_TYPE'where CPython says'_MISSING_TYPE'.type(MISSING)and__name__match. type(MISSING)()raisesTypeError: cannot create 'dataclasses._MISSING_TYPE' instances, where CPython builds a second, distinct object.default_factoryand__post_init__cannot suspend. Both run in a synchronous position the interpreter cannot preserve and resume, so calling an external function, anosfunction, or awaiting inside one raisesNotImplementedError: dataclass field default_factory: external function 'f' is not yet supported in this context(and the__post_init__equivalent). Ordinary in-sandbox code in them runs normally.ClassVar/InitVardetection is purely textual. Monty matches the annotation text (bare, dotted, subscripted, or quoted) without checking that the name is actually imported, where CPython resolves a string annotation through the defining module’s namespace. Soc: "ClassVar[int]"withoutClassVarin scope is excluded by Monty but is an ordinary field to CPython. Conversely any dotted spelling matches, so a same-named attribute on an unrelated module (mymod.ClassVar) is treated astyping.ClassVar.- A field holding a function or bound method reprs differently, since
Monty’s own
reprfor those differs (see classes.md). Only the text differs; the value and its equality match CPython. - A class-body
__setattr__never runs for the synthesized__init__, which writes fields straight into the instance__dict__. This is the never-dispatched attribute hook described in classes.md rather than something dataclass-specific, so@dataclassdoes not reject it. @dataclasson a non-class (e.g.dataclasses.dataclass(5)) raisesTypeError: dataclass() should be called on a class, not '<type>'. CPython instead raises an incidentalAttributeErrorabout__module__from its implementation. The@decosyntax only ever targets a class, so this affects only direct calls.dataclass(...)returns a native callable, not a Python function. CPython builds a closure, which Monty cannot: a native function has nowhere to keep the bound options but its own value. Applying it to a class is identical, and it reprs as<function dataclass at 0x..>, buttype()saysbuiltin_function_or_methodwhere CPython saysfunction, and CPython’s repr names the closure (dataclass.<locals>.wrap). Having nowhere to live but the value, the options are the value:dataclass(frozen=True) is dataclass(frozen=True)isTrue, where each CPython call builds a fresh closure. Fixable only if Monty gains closures over native functions; nothing else depends on that, so it is not planned.del obj.fieldon a frozen instance never raisescannot delete field, because Monty’s parser has nodelstatement at all. (Assignment matches CPython, message included, anddataclasses.FrozenInstanceErroris importable.)__dataclass_params__reads back normalised.C.__dataclass_params__exists, reprs like CPython’s and answers all ten flags, but each is theboolMonty acted on:@dataclass(frozen=1)reportsfrozen=Truewhere CPython echoes the1you passed.- The class’s metadata is read at use time, not built in at decoration.
CPython’s
@dataclassgenerates__init__,__eq__,__hash__,__repr__and__setattr__with the decoration’s choices built in, and leaves__dataclass_fields__and__dataclass_params__behind as records nothing reads again. Monty generates no methods. It acts on those two namespace entries and on__post_init__each time an instance is built, compared, hashed, printed or assigned to. Changing any of them after decoration therefore changes the class in Monty and nothing in CPython:- Rebinding
__dataclass_params__switches theeqandfrozenin force.C.__dataclass_params__ = Frozen.__dataclass_params__freezesCand makes it hashable; borrowing aneq=Falseclass’s params makesC(1) == C(1)false. Binding anything that is not a params object (None) putsCon the defaults,eq=True, frozen=False, unfreezing a frozen class. - Overwriting
__dataclass_fields__with a non-dict un-marks the class:is_dataclass(C)is false andC(...)constructs like a plain class. Rebinding it to another dict changes the fields, defaults and factories the next construction uses. Every default and factory is read out before the first factory runs, so a factory that rebinds it mid-construction changes nothing, as in CPython. - A
__post_init__added to a class that had none when decorated runs on the next construction; CPython’s generated__init__never calls it. Replacing a hook the class already had matches CPython, which also looksself.__post_init__up when it calls it. - Re-decorating rebuilds the class.
C = dataclass(frozen=True)(C)gives Monty a fully frozen class that constructs normally. CPython keeps the__init__its first decoration generated, which writes fields through the new frozen__setattr__and so raisesFrozenInstanceErroron construction.
- Rebinding
- No inheritance, so field inheritance across base dataclasses is unsupported (Monty has no class inheritance at all).
slots=True/weakref_slot=True— no__slots__, no weakrefs.