I'm writing a class in python and I have an attribute that will take a relatively long time to compute, so I only want to do it once. Also, it will not be needed by every instance of the class, so I don't want to do it by default in __init__
.
I'm new to Python, but not to programming. I can come up with a way to do this pretty easily, but I've found over and over again that the 'Pythonic' way of doing something is often much simpler than what I come up with using my experience in other languages.
Is there a 'right' way to do this in Python?
No, the getter will be called every time you access the property. Show activity on this post. Show activity on this post.
Caching is an optimization technique that you can use in your applications to keep recent or often-used data in memory locations that are faster or computationally cheaper to access than their source. Imagine you're building a newsreader application that fetches the latest news from different sources.
cached_property is a decorator that converts a class method into a property whose value is calculated once and then cached like a regular attribute. The cached value will be available until the object or the instance of the class is destroyed.
__slots__ is a class variable. If you have more than one instance of your class, any change made to __slots__ will show up in every instance. You cannot access the memory allocated by the __slots__ declaration by using subscription. You will get only what is currently stored in the list.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With