Functools cached_property
Sometimes the situation comes up where you have a function that is expensive to compute but only needs to be computed once and will be reused many times. This is a classic case of a situation where caching can be helpful. Since Python 3.8 a decorator has been introduced to functools that can transform a method on a class to a property that contains the computed result of the function call. You would define such a class like this:
from functools import cached_property
class Example:
def __init__(self, value):
self.value = value
@cached_property
def square_it(self):
"""This is just an example of something that would be expensive to compute"""
return self.value ** 2
Then we can use like this:
>>> a = Example(value=10)
>>> print(type(a.square_it))
<class 'int'>
>>> print(a.square_it)
100
As you can see this replaces the method square_it
with the return value of calling that function. This creates cache invalidation issues however:
>>> a.value = 2
>>> print(type(a.square_it))
<class 'int'>
>>> print(a.square_it)
100
Ultimately cache invalidation is still one of the hard problems of computer science, as the saying goes: "The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Roughly equivalent implementation
If you are looking for something like this by don't have python 3.8 or are just curious as to what's going on here's a roughly equivalent implementation:
"""This provides a decorator that lets you precompute expensive methods"""
class lazy_property:
"""
Allows lazy evaluation of properties of non-mutable data.
Useful when these are expensive to compute and are immutable.
The data must not be mutable because the method gets
replaced with the computed value in the original object.
"""
def __init__(self, fget):
self.fget = fget
self.func_name = fget.__name__
def __get__(self, obj, cls):
if obj is None:
return None
value = self.fget(obj)
setattr(obj, self.func_name, value)
return value