Dataclass-ABC is a Python library that bridges the gap between abstract base classes (ABCs) and dataclasses. It allows you to define and automatically implement abstract properties in dataclasses when these properties are overridden by fields.
Install Dataclass-ABC using pip:
pip install dataclassabc
The dataclassabc
decorator enables the use of abstract properties within dataclasses.
It resolves abstract properties defined in an abstract base class (ABC) and enforces their implementation through fields in the derived dataclass.
Here's how you can define an abstract property in an abstract class and implement it in a dataclass:
from abc import ABC, abstractmethod
from dataclassabc import dataclassabc
# Define an abstract base class with an abstract property
class A(ABC):
@property
@abstractmethod
def name(self) -> str: ...
# Use the dataclassabc decorator to implement the abstract property in a dataclass
@dataclassabc(frozen=True)
class B(A):
# Implementing the abstract property 'name'
name: str
# Works as expected
b1 = B(name='A')
# TypeError: B.__init__() missing 1 required positional argument: 'name'
b2 = B()
Using the standard dataclass
decorator to implement abstract properties will result in a exception, as shown below:
from abc import abstractmethod
from dataclasses import dataclass
class A:
@property
@abstractmethod
def name(self) -> str:
...
@dataclass(frozen=True)
class B(A):
name: str
# AttributeError: property 'name' of 'B' object has no setter
b = B(name='A')
Similar to the previous example, an exception is raised when using the dataclass
decorator with a class that inherits from ABC
:
from abc import ABC, abstractmethod
from dataclasses import dataclass
class A(ABC):
@property
@abstractmethod
def name(self) -> str:
...
@dataclass(frozen=True)
class B(A):
name: str
# TypeError: Can't instantiate abstract class B without an implementation for abstract method 'name'
b = B(name='A')
Using the slots=True
option with the dataclass
decorator results in unexpected behavior.
If no value is provided for the field, the abstract property is incorrectly used as the default value:
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class B(A):
name: str
# No exception is raised
b = B()
# The output will be <property object at ...>
print(b.name)
Adding fields that do not implement an abstract property can lead to a TypeError when the abstract properties are incorrectly treated as having default values:
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class B(A):
name: str
age: int
# TypeError: non-default argument 'age' follows default argument 'name'
b = B(age=12, name='A')
The dataclassabc library also supports defining mutable abstract properties. Use the @property decorator alongside a setter to define mutable properties in the abstract class:
from abc import ABC, abstractmethod
from dataclassabc import dataclassabc
class A(ABC):
@property
@abstractmethod
def name(self) -> str: ...
@name.setter
@abstractmethod
def name(self, val: str): ...
@dataclassabc
class B(A):
name: str