Caching class attributes in Python
Python ≥ 3.8
@property
and @functools.lru_cache
have been combined into @cached_property
.
import functools
class 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 functools
class 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 class
class 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*2
s = 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 time
class 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._bar
foo=Foo()
print "Accessing foo.bar"
print foo.bar
print "Accessing foo.bar"
print foo.bar
Python 3.8 includes the functools.cached_property
decorator.
Transform a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance. Similar to
property()
, with the addition of caching. Useful for expensive computed properties of instances that are otherwise effectively immutable.
This example is straight from the docs:
from functools import cached_property
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@cached_property
def stdev(self):
return statistics.stdev(self._data)
@cached_property
def variance(self):
return statistics.variance(self._data)
The limitation being that the object with the property to be cached must have a __dict__
attribute that is a mutable mapping, ruling out classes with __slots__
unless __dict__
is defined in __slots__
.
class MemoizeTest:
_cache = {}
def __init__(self, a):
if a in MemoizeTest._cache:
self.a = MemoizeTest._cache[a]
else:
self.a = a**5000
MemoizeTest._cache.update({a:self.a})