Explain Codes LogoExplain Codes Logo

Class inheritance in Python 3.7 dataclasses

python
dataclasses
inheritance
python-37
Alex KataevbyAlex Kataev·Feb 1, 2025
TLDR

To employ inheritance in Python dataclasses, the @dataclass decorator is simply applied to both the base and derived (child) classes. The derived class inherits the base class's attributes and can add its own. The fundamental code example is as follows:

from dataclasses import dataclass @dataclass class Base: common: int @dataclass class Derived(Base): specific: str instance = Derived(common=42, specific='derived class magic') print(instance) # Output: Derived(common=42, specific='derived class magic')

Here, the Derived class inherits the common field from the Base, augmenting it with the specific field. The beauty of dataclass inheritance is well illustrated in this example.

Unveiling the intricacies of inheritance

For a comprehensive grasp of inheritance in Python's dataclasses, it's critical to peel back the curtain and dig into the details:

Field declaration and the MRO

In dataclasses, always define fields via type hints for explicit attribute descriptions. Be cognizant of the Method Resolution Order (MRO). Non-default fields should precede those having defaults to elude the dreaded TypeError triggered by default arguments following non-default ones:

from dataclasses import dataclass, field @dataclass class Base: base_only: int # No magic tricks, just base class attribute shared: int = 0 @dataclass class Derived(Base): derived_only: str shared: int = field(default=10) # 'Fieldus-overridus-spell' to overwrite base field default

Implement post_init

To enable introspection of fields or validation after initialization, the __post_init__ can be invoked in your class, which is executed post __init__:

@dataclass class Base: common: int # No comic Sans here. def __post_init__(self): if self.common < 0: raise ValueError("common must be non-negative") # 'Common, you gotta be positive!' @dataclass class Derived(Base): specific: str # Anyone sniffing around for specifics?

Inheritance dance with field()

For base class fields possessing default values, the field() function should be harnessed to avert initialization idiosyncrasies. It ensures fields hit the dance floor (dataclass) in the right order and have their defaults assigned properly.

Diving deeper: Advanced concepts

For dealing with complex scenarios like a pro, allow me to introduce you to Praveen Kulkarni's extended answer, Eric Smith's enlightening blog and the wonders of the attrs library:

Keyword-only fields

Starting from Python 3.10, setting kw_only=True with @dataclass lets you define keyword-only fields. For those on Python 3.7, Eric Smith's blog provides a handy solution to achieve functionality similar to keyword-only fields:

# Python 3.10+ @dataclass(kw_only=True) class Base: common: int = field(default_factory=int, kw_only=True) # Eric Smith's solution for Python <3.10 @dataclass class Base: common: int = field(default=0, metadata={'back_to_future': True})

Handling multiple inheritance

Aim to neatly separate base classes for correct MRO order to prevent bumping into issues with field definition while inheriting from multiple bases:

@dataclass class FirstBase: a: int # There are no strings on me! @dataclass class SecondBase: b: str # Pardon my b-ehavior! @dataclass class Derived(FirstBase, SecondBase): c: float # Frankly, I could give a 'C' 😁

Embrace InitVar and factory methods

InitVar can prove helpful for additional parameters that are required at initialization but don't eventually turn into class fields. Moreover, factory methods can maintain a neat and clean class signature:

from dataclasses import dataclass, field, InitVar @dataclass class Base: setup: InitVar[int] # Fresh outta the oven! common: int = 0 def __post_init__(self, setup): if setup > 0: self.common = setup @dataclass class Derived(Base): specific: str # The beauty about being specific? No general questions! 😁 @staticmethod def create_with_default_common(specific, setup=1): return Derived(specific=specific, setup=setup)