#!/usr/local/bin/python ''' Sample test code: >>> f = Foo() >>> print f.x 1 >>> print f.y 2 >>> print f.z 2007-08-05 00:00:00 >>> print vars(f) {'_z': datetime.datetime(2007, 8, 5, 0, 0), '_y': 2, '_x': 1} >>> >>> b = Bar() >>> print b.x 1 >>> print b.y 2 >>> print b.z 2007-08-05 00:00:00 >>> print vars(b) {'_z': datetime.datetime(2007, 8, 5, 0, 0), '_y': 2, '_x': 1} >>> >>> z = Baz() >>> print z.x 1 >>> print z.y 2 >>> print z.z 2007-08-05 00:00:00 >>> print vars(z) {} ''' from datetime import datetime #------------------------------------------------------------------ def simple_curry(f, *args, **kws): '''Simple helper function for pre-bundling a function with some known arguments.''' def inner_curry(self): return f(self, *args, **kws) return inner_curry #================================================================== class CacheProperty(object): '''Caching mechanism that uses the supplied instance's namespace for storage.''' #-------------------------------------------------------------- def __init__(self, cache_var_name, initializer): self.initializer = initializer self.var = cache_var_name #-------------------------------------------------------------- def __set__(self, *args): raise AttributeError, u'Read-only attribute' #-------------------------------------------------------------- def __get__(self, instance, cls): if instance is None: raise AttributeError( u'%s must be accessed via instance' % (self.var,) ) if not hasattr(instance, self.var): # print '... No Cache' if callable(self.initializer): setattr(instance, self.var, self.initializer(instance)) else: setattr(instance, self.var, self.initializer) return getattr(instance, self.var) #================================================================== class CacheProperty2(object): '''Caching descriptor that stores the cached data itself.''' __NIL = object() #-------------------------------------------------------------- def __init__(self, initializer): self.initializer = initializer self.cache = self.__NIL #-------------------------------------------------------------- def __set__(self, *args): raise AttributeError, u'Read-only attribute' #-------------------------------------------------------------- def __get__(self, instance, cls): if instance is None: raise AttributeError, u'Instance level access only' if self.cache == self.__NIL: # print '... No Cache' if callable(self.initializer): self.cache = self.initializer(instance) else: self.cache = self.initializer return self.cache #================================================================== class Foo(object): '''A typical representation of the attribute caching mechanism in verbose fashion.''' #-------------------------------------------------------------- def expensive_function(self, arg): # do something expensive here return arg #-------------------------------------------------------------- def _get_x(self): if not hasattr(self, '_x'): self._x = self.expensive_function(1) return self._x #-------------------------------------------------------------- def _get_y(self): if not hasattr(self, '_y'): self._y = self.expensive_function(2) return self._y #-------------------------------------------------------------- def _get_z(self): if not hasattr(self, '_z'): self._z = datetime(2007,8,5) return self._z #-------------------------------------------------------------- x = property(_get_x) y = property(_get_y) z = property(_get_z) #================================================================== class Bar(object): '''Exhibit "A" for caching attributes, in this case the data is stored directly within the instance.''' #-------------------------------------------------------------- def expensive_function(self, arg): # do something expensive here return arg #-------------------------------------------------------------- x = CacheProperty('_x', simple_curry(expensive_function, 1)) y = CacheProperty('_y', simple_curry(expensive_function, 2)) z = CacheProperty('_z', lambda self: datetime(2007,8,5)) #================================================================== class Baz(object): '''Exhibit "B" for caching attributes, in this case the data is contained within the descriptor.''' #-------------------------------------------------------------- def expensive_function(self, arg): # do something expensive here return arg #-------------------------------------------------------------- x = CacheProperty2(simple_curry(expensive_function, 1)) y = CacheProperty2(simple_curry(expensive_function, 2)) z = CacheProperty2(lambda self: datetime(2007,8,5)) #------------------------------------------------------------------ def test(): # doctest does not seem to work in 2.3 import doctest doctest.testmod() ################################################################### if __name__ == '__main__': test()