Recipe 20.4. Caching Attribute ValuesCredit: Denis S. Otkidach ProblemYou want to be able to compute attribute values, either per instance or per class, on demand, with automatic caching. SolutionCustom descriptors are the right tools for this task: class CachedAttribute(object): ''' Computes attribute value and caches it in the instance. ''' def _ _init_ _(self, method, name=None): # record the unbound-method and the name self.method = method self.name = name or method._ _name_ _ def _ _get_ _(self, inst, cls): if inst is None: # instance attribute accessed on class, return self return self # compute, cache and return the instance's attribute value result = self.method(inst) setattr(inst, self.name, result) return result class CachedClassAttribute(CachedAttribute): ''' Computes attribute value and caches it in the class. ''' def _ _get_ _(self, inst, cls): # just delegate to CachedAttribute, with 'cls' as ``instance'' return super(CachedClassAttribute, self)._ _get_ _(cls, cls) DiscussionIf your class instances have attributes that must be computed on demand but don't generally change after they're first computed, custom descriptor CachedAttribute as presented in this recipe is just the ticket. Here is a toy example of use (with Python 2.4 syntax): class MyObject(object): def _ _init_ _(self, n): self.n = n @CachedAttribute def square(self): return self.n * self.n m = MyObject(23) print vars(m) # 'square' not there yet # emits: {'n': 23} print m.square # ...so it gets computed # emits: 529 print vars(m) # 'square' IS there now # emits: {'square': 529, 'n': 23} del m.square # flushing the cache print vars(m) # 'square' removed # emits: {'n': 23} m.n = 42 print vars(m) # emits: {'n': 42} # still no 'square' print m.square # ...so gets recomputed # emits: 1764 print vars(m) # 'square' IS there again # emits: {'square': 1764, 'n': 23} As you see, after the first access to m.square, the square attribute is cached in instance m, so it will not get recomputed for that instance. If you need to flush the cache, for example, to change m.n, so that m.square will get recomputed if it is ever accessed again, just del m.square. Remember, attributes can be removed in Python! To use this code in Python 2.3, remove the decorator syntax @CachedAttribute and insert instead an assignment square = CachedAttribute(square) after the end of the def statement for method square. Custom descriptor CachedClassAttribute is just a simple variant of CachedAttribute, easily obtained by inheritance: it computes the value by calling a method on the class rather than the instance, and it caches the result on the class, too. This may help when all instances of the class need to see the same cached value. CachedClassAttribute is mostly meant for cases in which you do not need to flush the cache because its _ _get_ _ method usually wipes away the instance descriptor itself: class MyClass(object): class_attr = 23 @CachedClassAttribute def square(cls): return cls.class_attr * cls.class_attr x = MyClass( ) y = MyClass( ) print x.square # emits: 529 print y.square # emits: 529 del MyClass.square print x.square # raises an AttributeError exception However, when you do need a cached class attribute with the ability to occasionally flush it, you can still get it with a little trick. To implement this snippet so it works as intended, just add the statement: class MyClass(MyClass): pass right after the end of the class MyClass statement and before generating any instance of MyClass. Now, two class objects are named MyClass, a hidden "base" one that always holds the custom descriptor instance, and an outer "subclass" one that is used for everything else, including making instances and holding the cached value if any. Whether this trick is a reasonable one or whether it's too cute and clever for its own good, is a judgment call you can make for yourself! Perhaps it would be clearer to name the base class MyClassBase and use class MyClass(MyClassBase), rather than use the same name for both classes; the mechanism would work in exactly the same fashion, since it is not dependent on the names of classes. See AlsoCustom descriptors are best documented at Raymond Hettinger's web page: http://users.rcn.com/python/download/Descriptor.htm. |