Reputation: 181
I would like to make a Block
class that has the following behavior: if iip3 is set, then oip3 is set to iip3 + gain. If oip3 is set, then iip3 is set to oip3 - gain like the following:
b = Block(gain = 10)
Block(gain=10, iip3=inf, oip3=inf)
b.oip3 = 30
Block(gain=10, iip3=20, oip3=30)
b.iip3 = 21
Block(gain=10, iip3=21, oip3=31)
I've tried this with the attrs module:
import attr
import numpy as np
def set_oip3(instance, attribute, iip3):
instance.oip3 = iip3 + instance.gain
return iip3
def set_iip3(instance, attribute, oip3):
instance.iip3 = oip3 - instance.gain
return oip3
@attr.s
class Block:
gain = attr.ib()
iip3 = attr.ib(on_setattr=set_oip3, default=np.inf)
oip3 = attr.ib(on_setattr=set_iip3, default=np.inf)
However, this doesn't work. I suspect that a call to set_oip3
results in a call to set iip3 which in turn calls set oip3.
There must be a solution using attrs.
Upvotes: 1
Views: 321
Reputation: 4116
As others said, it's not a great idea to mutate attributes when on attribute change. But if you insist, you can circumvent on_setattr
hooks by using object.__setattr__
– that's something attrs
does internally too:
import attr
import numpy as np
def set_oip3(instance, attribute, iip3):
object.__setattr__(instance, "oip3", iip3 + instance.gain)
return iip3
def set_iip3(instance, attribute, oip3):
object.__setattr__(instance, "iip3", oip3 - instance.gain)
return oip3
@attr.define
class Block:
gain = attr.ib()
iip3 = attr.ib(on_setattr=set_oip3, default=np.inf)
oip3 = attr.ib(on_setattr=set_iip3, default=np.inf)
Works as you asked for:
In [9]: b = Block(gain = 10)
In [10]: b
Out[10]: Block(gain=10, iip3=inf, oip3=inf)
In [11]: b.oip3 = 30
In [12]: b
Out[12]: Block(gain=10, iip3=20, oip3=30)
In [13]: b.iip3 = 21
In [14]: b
Out[14]: Block(gain=10, iip3=21, oip3=31)
Upvotes: 2
Reputation: 8962
You can use the @property
decorator:
import numpy as np
class Block:
def __init__(self, gain=10, iip3=np.inf, oip3=np.inf):
self.gain = gain
self._iip3 = iip3
self._oip3 = oip3
def __repr__(self):
return f"Block(gain={self.gain}, iip3={self.iip3}, oip3={self.oip3})"
@property
def iip3(self):
return self._iip3
@iip3.setter
def iip3(self, val):
self._iip3 = val
self._oip3 = self._iip3 + self.gain
@property
def oip3(self):
return self._oip3
@oip3.setter
def oip3(self, val):
self._oip3 = val
self._iip3 = self._oip3 - self.gain
Usage:
In [2]: b = Block()
In [3]: b.iip3 = 21
In [4]: b
Out[4]: Block(gain=10, iip3=21, oip3=31)
In [5]: b.oip3 = 30
In [6]: b
Out[6]: Block(gain=10, iip3=20, oip3=30)
For what it's worth, it's not a great idea to have a mutator produce side effects on attributes other than the ones a given mutator is supposed to be modifying.
Upvotes: 2