Welcome, guest | Sign In | My Account | Store | Cart

A cached property is a read-only property that is calculated on demand and automatically cached. If the value has already been calculated, the cached value is returned.

Python, 14 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def cached_property(f):
    """returns a cached property that is calculated by function f"""
    def get(self):
        try:
            return self._property_cache[f]
        except AttributeError:
            self._property_cache = {}
            x = self._property_cache[f] = f(self)
            return x
        except KeyError:
            x = self._property_cache[f] = f(self)
            return x
        
    return property(get)

Example:

class Swallow:
    def __init__(self, laden):
        self.mass = 5.0
        self.laden = laden

    def calc_air_speed_velocity(self):
        coconut_mass = 16.0 
        t0 = time.time()
        mass = self.mass + (coconut_mass if self.laden else 0.0)
        distance = 43
        time.sleep(mass*distance/1000.0) # must sleep after flying
        t = time.time() - t0
        return distance/t

    air_speed_velocity = cached_property(calc_air_speed_velocity)

s1 = Swallow(False)
s2 = Swallow(True)

print s1.air_speed_velocity
print s2.air_speed_velocity

print s1.air_speed_velocity  # notice that the second two invocations do not take any time.
print s2.air_speed_velocity

4 comments

David Lambert 15 years, 4 months ago  # | flag

It works as a decorator.

class Swallow:

    def __init__(self, laden):
        self.mass = 5.0
        self.laden = laden

    @cached_property
    def air_speed(self):
        coconut_mass = 16.0 
        t0 = time.time()
        mass = self.mass + (coconut_mass if self.laden else 0.0)
        distance = 43
        time.sleep(mass*distance/400.0) # must sleep after flying
        t = time.time() - t0
        return distance/t
runsun pan 14 years, 10 months ago  # | flag

This might be of interest:

Easy Property Creation in Python http://code.activestate.com/recipes/576742/

Only 7 lines of code, easy to understand, easy to use, easy to customize, save you a lot of typing

Giampaolo RodolĂ  10 years, 3 months ago  # | flag

Thanks for writing this. Here's a slightly modified version using functools.wraps() which preserves the original property docstring and name:

import functools

def cached_property(fun):
    """A memoize decorator for class properties."""
    @functools.wraps(fun)
    def get(self):
        try:
            return self._cache[fun]
        except AttributeError:
            self._cache = {}
        except KeyError:
            pass
        ret = self._cache[fun] = fun(self)
        return ret
    return property(get)
Andras Gefferth 9 years, 10 months ago  # | flag

Another modification/improvement: you can define member variable names, whose change should trigger the recalculation of the cached value.

def smart_cached_property(inputs=[]):
    def smart_cp(f):
        @functools.wraps(f)
        def get(self):
            input_values = dict((key,getattr(self,key)) for key in inputs )
            try:
                x = self._property_cache[f]
                if input_values == self._property_input_cache[f]:
                    return x
            except AttributeError:
                self._property_cache ={}
                self._property_input_cache = {}
            except KeyError:
                pass

            x = self._property_cache[f] = f(self)
            self._property_input_cache[f] = input_values

            return x
        return property(get)
    return smart_cp

Usage example:

@smart_cached_property(['mass','laden'])
def air_speed_velocity(self):
    ...
    ...
Created by Ken Seehart on Thu, 13 Nov 2008 (MIT)
Python recipes (4591)
Ken Seehart's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks