I am having the following Python problem:
I have a class with different attributes that depend on each other. If an attribute is changed on runtime, some attributes should change as well. I am aware of getters
and the @property
decorator.
However, in this particular case, there are attributes that will take a long time to be computed, because they involve solving some integrals numerically. I would like not to recompute them on each call as this would make the code very inefficient, but rather detect changes of the other attributes and only recompute if needed.
The code looks like this. For simplicity, I only have two attributes here.
class MyClass
def __init__(self, a):
self.a = a
self.b = None
@property
def b():
if b is not None:
return b
else:
self.compute_b() # takes a long time.
return b
def compute_b(self):
# compute b using self.a
self.b = b
So what I want to do is detect changes of certain attributes and then reset the dependent attribute to None
and only recompute them when needed.
I came up with a few ideas, but I am not happy with any of them. I will use again a
and b
as names for the respective attributes.
Use @property
Recompute b
each time it is called. This is conceptionally the easiest solution, however also the slowest.
Write a setter(a)
for a
I could do this. However, some attributes are inherited from other classes. So I would have to write setters
in the derived class which seems a bit odd. I feel like this makes the code much less readable and it might behave in unexpected ways.
Overload __setattr__
I could overload __setattr__
in my class and reset b
inside this special method if a
has changed. I have, however, read that this is not good practice. E.g. if attributes are initialized in the wrong order I might reset them, even though it's not needed.
Make a copy of a
when b
is computed.
Make a copy of a
when b
is computed. Before returning a precomputed b
check if a
has changed. Basically setting a flag when computing b
.
Use @functools.lru_cache
[https://docs.python.org/3/library/functools.html#functools.lru_cache]
This looks also quite promising, however, it seems like I have to pass arguments to my methods for it to work and I would like not to do that as there are many attributes that are needed. Otherwise, it is pointless to use a class in the first place.
I feel like I could make any of these ideas work, but none of them seem to be optimal.
Does anyone know how to deal with this problem in a cleaner fashion?