Let's suppose that I have a dataclass A. The dataclass A contains some property with a default value.
Let's suppose that I want to extend that dataclass with a dataclass B. I need to add some non-default property to the dataclass B.
However, if I do that, I will get an error: "Non-default property follows a default property in class B". That is because dataclass generates firstly the properties from the class A and then from the class B. So the non-default property declared in the class B follows the default property from the class A (in the auto generated init function for example).
Example:
from dataclasses import dataclass
@dataclass
class A:
a: int
b: int = 5
@dataclass
class B(A):
c: int
The code above will give an error.
How can I therefore add a non-default property to the class B? What are the possible ways to do that without throwing an error?
You have a first problem that doing that is not possible in Python as a language at all: when creating a function, non-default parameters always have to precede parameters that have a default. It turns out it makes sense.
If it were possible to create a class like this, the signature to creating "B" would necessarily have to be keyword only - as it would not be possible to assign a value to ".c" without giving a value to ".b" - at which point its default makes no sense. But still, the language would allow one to call B(a=10, c=20)
and then b could use its default of 5.
So, the most sensible thing to do seems to be to add a default value to your "non-default" attributes: but use a sentinel value, and then detect in the post_init stage if an initial value was not given to these "pseudo-defaulted" fields and raise an error.
It would be possible to wrap the dataclass decorator itself in another decorator that could do the introspection, create the post-init code, and do all of it alone: but that is a lot of work to let things less explicit (still, it could be valid if you are using this pattern a lot).
Otherwise, just add these steps manually:
from dataclasses import dataclass
FALSEDEFAULT = object()
@dataclass
class A:
a: int
b: int = 5
@dataclass
class B:
c: int = FALSEDEFAULT # one might have problem with type-checkers like mypy
def __post_init__(self):
if self.c is FALSEDEFAULT:
raise TypeError("An argument for "c" has to be passed when creating B instances")
An intermediary automation step - more useful than this if you are reusing it, and far simpler than wrappign dataclass
itself might be to have a base-class with a __post_init__
that will check the values of all fields and raise on any field that contains the "PSEUDODEFAULT" sentinel -
class OutOfOrderBase:
def __post_init__(self):
for name, field in self.__dataclass_fields__.items():
if getattr(self, name, None) is PSEUDODEFAULT: raise...