property question

Bruno Desthuilliers bruno.42.desthuilliers at
Tue Oct 9 13:55:47 CEST 2007

Manu Hack a écrit :
> hi all,
> If I have a class A with A.x, A.y, A.z.  A.y and A.z are property and
> in order to compute the value of them, A.y depends on A.x while A.z
> depends on A.y and A.x.  If I call A.y, and A.z, the value A.y would
> be computed twice.  Is there a smart way to avoid that as to A.y will
> be recomputed only if A.x has been changed?  Now I can define more
> variables to keep track of what is changed but when there are more
> variables and the dependency becomes more involved it could be very
> complicated.  Thanks a lot.

A Q&D solution is to use a local cache (usually a dict with propnames as 
keys), and have any 'setter' invalidate that cache, ie:

def cached_property(fn):
   propname = fn.__name__
   def fget(self):
     return self._from_cache(propname, fn)
   def fset(self, val):
     raise AttributeError("%s.%s is readonly" % (self, propname))
   return property(fget, fset)

class A(object):
   def __init__(self, x):
     self._cache = {}
     self.x = x

   def _from_cache(self, name, fn):
       return self._cache[name]
     except KeyError:
       val = fn(self)
       self._cache[name] = val
       return val

   def _invalidate_cache(self, *names):
     for name in names:
         del self._cache[name]
       except KeyError, e:
         #print "%s : %s not in %s" % (e, name, self._cache)

   def x():
     def fget(self):
       return self._x
     def fset(self, x):
       self._x = x
       # dependencies here - would be nice to have
       self._invalidate_cache('y', 'z')
     return property(**locals())

   def y(self):
     return self.x + 2

   def z(self):
     return self.x * (self.y / 2.0)

The remaining problem is that here, it's x that have the knowledge of 
who depends on it. This knowledge would be better expressed as a param 
or the @cached_property decorator and stored somewhere, so that the call 
to _invalidate cache would take the name of the property itself (here 
'x') instead of the list of dependent cached properties. This can be 
done at least with a custom metaclass - implementation left as an 
exercice to the reader !-)

More information about the Python-list mailing list