Janis Lesinskis' Blog

Assorted ramblings

  • All entries
  • About me
  • Projects
  • Economics
  • Misc
  • Software-engineering
  • Sports

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
Published: Sun 12 February 2023
By Janis Lesinskis
In Software-engineering
Tags: python functools decorators caching cache-invalidation

links

  • JaggedVerge

social

  • My GitHub page
  • LinkedIn

Proudly powered by Pelican, which takes great advantage of Python.