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:
...
def __post_init__(self):
object.__setattr__(self, 'sort_index', 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)
...
Which, given your testcase, returns
Jack and 35
Jason and 45
James and 60
for me.
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.
I know maybe this answer is more generic, but it could help as it's more simple than the other answer, and doesn't use any getter or setter Simply use __post_init__
import random
from dataclasses import dataclass, FrozenInstanceError
@dataclass(repr=True, eq=True, order=False, unsafe_hash=False, frozen=True)
class Person:
name: str
age: int = None
def __post_init__(self):
object.__setattr__(self, 'age', self.calc_age())
@staticmethod
def calc_age():
return random.randint(0, 100)
if __name__ == '__main__':
person = Person("Fede")
person2 = Person("Another")
print(person)
print(person2)
try:
person.age = 1
except FrozenInstanceError:
print("can't set age ")
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.