[Python-ideas] caching properties

George Sakkis george.sakkis at gmail.com
Fri May 1 01:27:13 CEST 2009


On Thu, Apr 30, 2009 at 4:19 PM, spir <denis.spir at free.fr> wrote:

> (Aside from the hashable issue pointed by Arnaud)
> I wonder about having the whole parameter tuple as key for caching.
> In packrat parsing, you may have more than one parameter (including the source, indeed) but only one is relevant for memoizing (the position). Cache has to be reset anyway when starting a new parse, so that having the source in keys is irrelevant.
> Typically, if not a single value, I guess saved results form a simple array depending on an ordinal.

I think It's clear by now that caching in the general case is not
trivial, both in terms of API and implementation. That makes the
original request - caching properties only - more appealing since most
problems go away if there are no parameters.

In the simplest case where cache expiration is not supported,
cachedproperty can be a 7-line decorator:

def cachedproperty(fget):
    def fget_wrapper(self):
        try: return fget_wrapper._cached
        except AttributeError:
            fget_wrapper._cached = value = fget(self)
            return value
    return property(fget_wrapper, doc=fget.__doc__)

Cache expiration can be exposed by (ab)using the deleter; "del
obj.prop" looks much better than "ObjType.prop.fget.expire(obj)":

def cachedproperty(fget):
    def fget_wrapper(self):
        try: return fget_wrapper._cached
        except AttributeError:
            fget_wrapper._cached = value = fget(self)
            return value
    def fdel(self):
        try: del fget_wrapper._cached
        except AttributeError: pass
    return property(fget_wrapper, fdel=fdel, doc=fget.__doc__)

And finally here's a general version that supports properly the new in
2.6 getter()/setter()/deleter() methods (note that setting a property
expires the cache, just like delete):

class cachedproperty(property):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        if fget is not None:
            def fget_wrapper(obj):
                try: return self._cached
                except AttributeError:
                    self._cached = value = fget(obj)
                    return value
        else:
            fget_wrapper = None
        if fset is not None:
            def fset_wrapper(obj, value):
                fset(obj,value)
                try: del self._cached
                except AttributeError: pass
        else:
            fset_wrapper = None
        if fdel is not None:
            def fdel_wrapper(obj):
                fdel(obj)
                try: del self._cached
                except AttributeError: pass
        else:
            def fdel_wrapper(obj):
                try: del self._cached
                except AttributeError: pass
        super(cachedproperty,self).__init__(fget_wrapper,
fset_wrapper, fdel_wrapper, doc)
        self.__doc__ = doc or getattr(fget, '__doc__', None)
        # store for getter() / setter() / deleter()
        self._fget, self._fset, self._fdel = fget, fset, fdel

    def getter(self, getter):
        return self.__class__(getter, self._fset, self._fdel, self.__doc__)

    def setter(self, setter):
        return self.__class__(self._fget, setter, self._fdel, self.__doc__)

    def deleter(self, deleter):
        return self.__class__(self._fget, self._fset, deleter, self.__doc__)


George



More information about the Python-ideas mailing list