ActiveState Code

Recipe 363602: Lazy property evaluation


Lazy properties can be easily built in Python 2.4 -- properties whose value may require some effort to calculate, but whose values remain constant once calculated. This recipe uses decorators to implements such properties.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Lazy(object):
    def __init__(self, calculate_function):
        self._calculate = calculate_function

    def __get__(self, obj, _=None):
        if obj is None:
            return self
        value = self._calculate(obj)
        setattr(obj, self._calculate.func_name, value)
        return value


# Sample use:

class SomeClass(object):

    @Lazy
    def someprop(self):
        print 'Actually calculating value'
        return 13


o = SomeClass()
o.someprop
o.someprop

Discussion

In a recent discussion on comp.lang.python, there was discussion about how to provide properties which get calculated once, where you want to avoid the work of doing the calculation at all if you never use the property, but where you get simple access to the property once calculated. This technique evolved from a suggestion by Lief K-Brooks that looked to be something that could be made into an easy-to-use facility with decorators.

Comments

  1. 1. At 3 p.m. on 22 jan 2005, Alexander Semenov said:

    Elegant method to invalidate calculated value. Now, after one execution calc-function lost. It's interesting to add some method to invalidate and recalculate property.

  2. 2. At 1:45 p.m. on 6 feb 2005, Scott David Daniels (the author) said:

    Retracting a value is very simple. Just delete the attribute of the object. Using the sample above:

    >>> o = SomeClass()
    >>> o.someprop
    Actually calculating value
    13
    >>> o.someprop
    13
    >>> del o.someprop
    >>> o.someprop
    Actually calculating value
    13
    >>> o.someprop
    13
    
  3. 3. At 7:42 a.m. on 5 dec 2008, Daniel Miller said:

    Or if you don't feel like implementing the Lazy descriptor:

    class SomeClass(object):
    
        @property
        def someprop(self, v=[]):
            if not v:
                print 'Actually calculating value'
                v[0] = 13
            return v[0]
    
  4. 4. At 8:02 a.m. on 5 dec 2008, Daniel Miller said:

    Argh! Always test before you post (stupid me). The previous version's value was cached on the class, not the instance. Here's a version that works more like the original:

    class SomeClass(object):
    
        @property
        def someprop(self):
            if "someprop" not in self.__dict__:
                print 'Actually calculating value'
                value = 13
                self.__dict__["someprop"] = value
            return self.__dict__["someprop"]
    

    That's starting to get ugly. Oh, and the original solution is better if you need to invalidate the cached value. If you want to invalidate this one you'll need to invoke this incantation:

    >>> del s.__dict__["someprop"]
    

Sign in to comment