Reputation: 643
Consider the following class:
from dataclasses import dataclass
@dataclass
class C:
a: int = 1
b: int
Trying to execute this yields TypeError: non-default argument 'b' follows default argument
Now consider this:
from dataclasses import dataclass, field
from dataclasses_json import config
@dataclass
class C:
a: int = field(metadata=config(encoder=lambda x: x, decoder=lambda x: x))
b: int
This executes without error.
The question is: how does the field
function "cheat" the python interpreter and not considered a default value? Can I replicate this behavior in my own function?
Upvotes: 0
Views: 1241
Reputation: 50086
The @dataclass
is not "interpreted by the interpreter", in the sense that the interpreter does not "know" it has to raise errors like TypeError: non-default argument 'b' follows default argument
. Instead, @dataclass
is a regular Python function that inspects the class object and explicitly raises the error.
The high-level descriptions of this mechanism is that field
returns a Field
object containing the meta-data passed to field
. The @dataclass
code checks if class attribute values are Field
objects, not whether they are created by field
– one can write a custom function to construct a Field
instance if needed.
Of course, the easiest approach is just to have a function that calls field
in order to create a Field
.
from dataclasses import field, MISSING
def auto_field(*, default=MISSING, default_factory=MISSING, init=True, metadata=None):
"""Field that inspects defaults to decide whether it is repr/hash'able"""
if default is MISSING and default_factory is MISSING:
return field(init=init, metadata=metadata)
test_default = default if default is not MISSING else default_factory()
return field(
default=default, default_factory=default_factory, init=init, metadata=metadata,
repr=type(test_default).__repr__ is not object.__repr__,
hash=getattr(test_default, '__hash__', None) is not None,
compare=getattr(test_default, '__hash__', None) is not None,
)
from dataclasses import dataclass
@dataclass(frozen=True)
class Foo:
a: int = auto_field() # not counted as a default
b: int
c: list = auto_field(default_factory=list)
print(Foo(12, 42), hash(Foo(12, 42))) # works because c is ignored for hashing
Note that conceptually, one is still restricted to the logic of dataclass
and its Field
s. For example, that means that one cannot create a "field which has a default but is not considered a default value" – depending on how one approaches it, dataclass
would either ignore it or still raise an error when preparing the actual class.
Upvotes: 2