0

Following an answer from this previous question about memoizing all of a class’s methods by default, I’m using this metaclass:

from inspect import isfunction

class Immutable(type):
    def __new__(cls, name, bases, dct):
        for key, val in dct.items():
            # Look only at methods/functions; ignore those with
            # "special" names (starting with an underscore)
            if isfunction(val) and val.__name__[0] != '_':
                dct[key] = memoized(val)
            elif hasattr(val, 'fget'):
                # It's a property; we currently can't memoize these
                pass
        return type.__new__(cls, name, bases, dct)

This metaclass has the effect of applying the decorator memoized to all of the functions and methods in a class. The only problem is that it doesn’t work with properties. (The classes that use this metaclass are treated as immutable, so I’m only worried about readonly properties here.) If I try to do something like

from time import sleep

class Foo(object):
    __metaclass__ = Immutable

    @property
    def slow_calculation(self):
        sleep(5)    # simulate an intensive calculation
        return 4

then slow_calculation is not memoized—there’s a five-second delay each time it’s called, not just the first time.

The issue is that property() returns a property object, not a function, and so the isfunction test in the metaclass’s __new__ won’t catch properties. As you can see I added a test that finds properties, but then I can’t figure out how to apply the memoized decorator to the property getter fget. I thought maybe I could say

elif hasattr(val, 'fget'):
    val.fget = memoized(val.fget)
    dct[key] = val

but in this case I get

Traceback (most recent call last):
  [...]
  File "utils.py", line 54, in __new__
    val.fget = memoized(val.fget)
TypeError: Error when calling the metaclass bases
    readonly attribute

Is there some other way to apply a decorator to a property getter?

Community
  • 1
  • 1
bdesham
  • 15,430
  • 13
  • 79
  • 123

1 Answers1

2
prop = property(memoized(prop.fget), prop.fset, prop.fdel, prop.__doc__)

Don't modify the property; make a new one.

user2357112
  • 260,549
  • 28
  • 431
  • 505