6

Consider the following minimal example:

class Foo(object):

    def __init__(self):
        self.b = self.a = 1

    @property
    def sum(self):
        print 'Recalculating sum'
        return self.a + self.b

foo = Foo()
print foo.sum
print foo.sum   # Prints 'Recalculating sum' even though neither a or b has changed since previous call
foo.a = 2
print foo.sum   # a has been changed to 2 so recalculation is necessary

I would like to memoize sum such that if self.a and self.b doesn't change, then we don't need to keep recalculating the property.

The property should only be recalculated when either self.a or self.b has changed -- is there an simple way to do this?

3 Answers 3

15

python3:

from functools import lru_cache as memoized

@property
@memoized(maxsize=1)
def sum(self):
    return self.a + self.b

python 3.8

from functools import cached_property

@cached_property
def sum(self):
    return self.a + self.b
Sign up to request clarification or add additional context in comments.

2 Comments

There's also @cached_property (from 3.8 on). I'm not sure how it differs from stacking @property on top of @cache and which is better for which use case. The docs explain it to some extent.
This approach is not appropriate here. Because a and b are not arguments to the function, they will not be considered by the memoization. You will always get the same result even if a and b change.
2

Use properties for a and b too and clear up your cache in the setters:

class Foo(object):

    def __init__(self):
        self.a = 1
        self.b = 1

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = value
        self._clearsum()

     @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b = value
        self._clearsum()

    def _clearsum(self):
        self._sum = None

    @property
    def sum(self):
        if self._sum is None:
            self._sum = self.a + self.b
        return self._sum

Or if you want something a bit more generic, you can check this too: Storing calculated values in an object

Edit : someone recently suggested adding self._sum = None in __init__ to "avoid an error when accessing sum", but that's actually not necessary - __init__ invokes a.setter, which invokes _clearsum, which sets the _sum attribute, so it's garanteed self._sum will be created whatever.

1 Comment

This is the one that actually does what the question asks for.
0

there is a module that does this. Pypi link here: https://pypi.org/project/memoized-property/ For the above code I have this with using the module:

In [2]: from memoized_property import memoized_property                                                                                                       

In [3]: class test():  
   ...:         def __init__(self):  
   ...:             self.a = 0  
   ...:             self.b = 0  
   ...:        @memoized_property  
   ...:        def sum(self):  
   ...:           print('calculating...')  
   ...:           return self.a + self.b  

In [4]: t=test()                                                                                                                                              

calculating...
In [5]: t.sum                                                                                                                                                 
Out[5]: 0

In [7]: t.a=5                                                                                                                                                 

In [8]: t.sum                                                                                                                                                 
Out[8]: 0

2 Comments

This doesn't consider the values of a and b since they are not arguments to the function. You'll only get the same result. In fact, your example shows this. The question wants the property to update when you update the attributes of the instance.
@kindall i think my answer then does not address the op, delete?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.