Caching class attributes in Python
Python ≥ 3.8@property
and @functools.lru_cache
have been combined into @cached_property
.
import functoolsclass MyClass: @functools.cached_property def foo(self): print("long calculation here") return 21 * 2
Python ≥ 3.2 < 3.8
You should use both @property
and @functools.lru_cache
decorators:
import functoolsclass MyClass: @property @functools.lru_cache() def foo(self): print("long calculation here") return 21 * 2
This answer has more detailed examples and also mentions a backport for previous Python versions.
Python < 3.2
The Python wiki has a cached property decorator (MIT licensed) that can be used like this:
import random# the class containing the property must be a new-style classclass MyClass(object): # create property whose value is cached for ten minutes @cached_property(ttl=600) def randint(self): # will only be evaluated every 10 min. at maximum. return random.randint(0, 100)
Or any implementation mentioned in the others answers that fits your needs.
Or the above mentioned backport.
I used to do this how gnibbler suggested, but I eventually got tired of the little housekeeping steps.
So I built my own descriptor:
class cached_property(object): """ Descriptor (non-data) for building an attribute on-demand on first use. """ def __init__(self, factory): """ <factory> is called such: factory(instance) to build the attribute. """ self._attr_name = factory.__name__ self._factory = factory def __get__(self, instance, owner): # Build the attribute. attr = self._factory(instance) # Cache the value; hide ourselves. setattr(instance, self._attr_name, attr) return attr
Here's how you'd use it:
class Spam(object): @cached_property def eggs(self): print 'long calculation here' return 6*2s = Spam()s.eggs # Calculates the value.s.eggs # Uses cached value.
The usual way would be to make the attribute a property and store the value the first time it is calculated
import timeclass Foo(object): def __init__(self): self._bar = None @property def bar(self): if self._bar is None: print "starting long calculation" time.sleep(5) self._bar = 2*2 print "finished long caclulation" return self._barfoo=Foo()print "Accessing foo.bar"print foo.barprint "Accessing foo.bar"print foo.bar