Reputation: 133
I have a situation where I need to store variables a,b, and c together in a dataclass, where c = f(a,b)
and a,b can be mutated. I need c
to be displayed along with a and b when printing the object, and c
cannot be changed except through changing a
or b
.
I felt the best way to do this was to have c
be a value created with a property.
Minimal example of what I tried to use:
@dataclass
class Demo:
a:int
b:int
c:int = field(init=False)
@property
def c(self) -> int:
return self.a * self.b
However, calling this results in AttributeError: can't set attribute
. I suspect the code is trying to set c to field(init=False)
, and failing because c doesn't have a setter.
Options I've considered (not counting alternatives I tried that makes the code crash in a different manner):
c
in init
@c.setter
that does nothing
sys._getframe().f_back.f_code
in the setter to raise an error if the caller is not __init__
(function at endnote)
c
be created in __post_init__
, and use dataclasses.replace
to modify a and b.
Demo
(well, the actual thing, not the minimal example) require it to be mutable.endnote:
@c.setter
def c(self, val):
caller = sys._getframe().f_back.f_code.co_name
if caller != '__init__':
raise ValueError # or some other error
Upvotes: 3
Views: 4353
Reputation: 168776
Your method c
overwrites your field c
-- the original field
declaration is lost, including the init=False
.
Try changing the order and using the default=
parameter.
from dataclasses import dataclass, field
@dataclass
class Demo:
@property
def c(self) -> int:
return self.a * self.b
a: int
b: int
c: int = field(init=False, default=c)
d = Demo(5, 10)
print(d)
d.a = 10
print(d)
d.c = 4 # generates an AttributeError
Output from Python 3.8.0:
Demo(a=5, b=10, c=50)
Demo(a=10, b=10, c=100)
Traceback (most recent call last):
...
File "/home/rob/src/plot/dc.py", line 20, in <module>
d.c = 4 # generates an AttributeError
AttributeError: can't set attribute
Upvotes: 4
Reputation: 281683
If you want c
to be a computed property, then take out the c
field entirely and just leave the property:
@dataclass
class Demo:
a:int
b:int
@property
def c(self) -> int:
return self.a * self.b
Fields are for stored data, which c
should not be.
If you want c
to show up in repr
, you'll have to write the __repr__
method yourself. If later code needs c
to show up in fields
, then you probably need to rework your code at a deeper level, and possibly switch to something other than dataclasses
.
Upvotes: 5