I was just playing around with the concept of Python dataclasses and abstract classes and what i am trying to achieve is basically create a frozen dataclass but at the same time have one attribute as a property. Below is my code for doing so:
import abc
from dataclasses import dataclass, field
class AbsPersonModel(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def age(self):
...
@age.setter
@abc.abstractmethod
def age(self, value):
...
@abc.abstractmethod
def multiply_age(self, factor):
...
@dataclass(order=True, frozen=True)
class Person(AbsPersonModel):
sort_index: int = field(init=False, repr=False)
name: str
lastname: str
age: int
_age: int = field(init=False, repr=False)
def __post_init__(self):
self.sort_index = self.age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0 or value > 100:
raise ValueError("Non sensical age cannot be set!")
self._age = value
def multiply_age(self, factor):
return self._age * factor
if __name__ == "__main__":
persons = [
Person(name="Jack", lastname="Ryan", age=35),
Person(name="Jason", lastname="Bourne", age=45),
Person(name="James", lastname="Bond", age=60)
]
sorted_persons = sorted(persons)
for person in sorted_persons:
print(f"{person.name} and {person.age}")
When i run this i get the below error:
Traceback (most recent call last):
File "abstract_prac.py", line 57, in <module>
Person(name="Jack", lastname="Ryan", age=35),
File "<string>", line 4, in __init__
File "abstract_prac.py", line 48, in age
self._age = value
File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field '_age'
How can i get the best of both worlds(dataclasses and also using property along with it)?
Any help would be much appreciated.
You can do what the frozen
initialisator in dataclasses itself does and use object.__setattr__
to assign values. Given your abstract class, this dataclass definition should work:
@dataclass(order=True, frozen=True)
class Person:
sort_index: int = field(init=False, repr=False)
name: str
lastname: str
age: int
_age: int = field(init=False, repr=False) # can actually be omitted
def __post_init__(self):
object.__setattr__(self, 'sort_index', self.age)
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0 or value > 100:
raise ValueError("Non sensical age cannot be set!")
object.__setattr__(self, '_age', value)
def multiply_age(self, factor):
return self._age * factor
Running your test suite should now return the expected
Jack and 35
Jason and 45
James and 60
This works because setting a dataclass to frozen
disables that class' own __setattr__
and makes it just raise the exception you saw. Any __setattr__
of its superclasses (which always includes object
) will still work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With