python: are property fields being cached automatically?

Viewed 14989

My question is are the following two pieces of code run the same by the interpreter:

class A(object):
  def __init__(self):
     self.__x = None

  @property
  def x(self):
     if not self.__x:
        self.__x = ... #some complicated action
     return self.__x

and the much simpler:

class A(object):
  @property
  def x(self):
      return ... #some complicated action

I.e., is the interpreter smart enough to cache the property x?

My assumption is that x does not change - finding it is hard, but once you find it once there is no reason to find it again.

8 Answers

To anyone who might be reading this in 2020, this functionality is now available in the funcutils module as part of the standard library as of Python 3.8.

https://docs.python.org/dev/library/functools.html#functools.cached_property

Important to note, classes that define their own __dict__ (or do not define one at all) or use __slots__ might not work as expected. For example, NamedTuple and metaclasses.

I've had to look it up, since I had this same question.

The functools package from the standard library will be getting a cached_property decorator as well. Unfortunately, it's only available from Python 3.8 (as of time of this post, it's 3.8a0). The alternative to waiting is to use a custom one, such as this one as mentioned by 0xc0de) or Django's, for now, then switch later:

from django.utils.functional import cached_property
# from functools import cached_property # Only 3.8+ :(

The decorator from Denis Otkidach mentioned in @unutbu's answer was published in O'Reilly's Python Cookbook. Unfortunately O'Reilly doesn't specify any license for code examples – just as informal permission to reuse the code.

If you need a cached property decorator with a liberal license, you can use Ken Seehof's @cached_property from ActiveState code recipes. It's explicitly published under the MIT license.

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)

Note: Adding for the sake of completeness of available options.

No, property is not cached by default. However there are several options to get that behaviour, I would like to add one more to that:

https://github.com/pydanny/cached-property

Related