"""Laziness. Where an attribute of an object isn't guaranteed to be needed, takes non-trivial computation to determine when needed and may be needed more than once, it makes sense to not compute it until needed, but remember it thereafter. I call this lazy evaluation of the attribute. The value, once computed, can either be stored in __dict__ - which is what the lazy builder does - or in a dictionary private to the lazy mechanism: in the latter case - the cache builder - it makes sense to support a `clear the lazy data' method, __flush__(), which takes no arguments. Naturally, the way to lazily compute a value is to call a function stored on a sub-object of the class (just like methods on .__meth__), using the name of the desired attribute as the name of the function. For lazy, this sub-object is called __lazy__; for cache it is called __cache__. Where an attribute is implemented by calling a function, two arguments are passed: * the object whose attribute it is to be; and * the name of the attribute. Although the function was likely selected by asking for that name from some ancillary object (the class's lazy method carrier), it sometimes makes sense to allow several attributes to be implemented by one function - e.g. if computing any one of them involves computing the rest, so you may as well store all. Usage sketch: class Small (Prior, Lazy): instance __lazy__ (__lazy__): def one(self, ignored): return 1 def two(self, ignored): return 2 def many(self, ignored): return self.everywhere def __init__(self, *, **, up=__init__): up(self) # calls Prior.__init__ self.everywhere = 0 def mymeth(self, here, there): self.everywhere = here + there return there - here """ from bonnet import aslookup from python import builder, class, instance builder lazy (class,): instance __magic__ (__magic__,): # src will be wrap.__class__.__lazy__ def lazy(src, wrap, lookups): """Lazy attribute computation scheme. A lazy class carries around a __lazy__ sub-object. Each non-magic attribute of this should be a callable taking two arguments. If an instance of the lazy class lacks an attribute (in its __dict__), but the .__lazy__ of the class has an attribute of that name, this last is called, with the instance as first argument and attribute name as second (so that .__lazy__ can use one method to compute several attributes): the result is used as the instance's attribute of the given name and stored in the __dict__ of the instance to save re-computation. Thus: for an instance, obj, of a lazy class; computation of an attribute, e.g. obj.name, will be performed by invoking: obj.__class__.__lazy__.name(obj, 'name') if the attribute is not found in obj.__dict__ or borrowed from any base of obj. """ def getit(key, obj=wrap, meths=aslookup(src)): # don't want to inadvertently pick up __class__.__lazy__'s magic # attributes ... it has, at least, dict, lookups and del. if key == '__dir__': return meths(key) if key[:2] == '__' == key[-2:]: raise AttributeError, key ans = meths(key)(obj, key) # don't mind if that AttributeError()ed ... setattr(obj, key, ans) return ans lookups.append(getit) # arguably insert(0, ...) __lazy__ = None lazy Lazy: """Base-class implementing lazy look-up.""" builder cache (class,): instance __magic__ (__magic__,): # src will be wrap.__class__.__cache__ def cache(src, wrap, lookups): """Cached lazy attribute computation. Attributes are computed as for a lazy class, but stored in a dictionary which is consulted before attempting such computation; this dictionary may be cleared by invoking the .__flush__() `method' of an instance of the cached class. """ stash = {} def early(key, bok=stash): # don't contribute to __dir__; it'd only duplicate getit's contribution if key == '__flush__': return bok.clear # magic method try: return bok[key] except KeyError: raise AttributeError, key def getit(key, obj=src, meths=aslookup(wrap), bok=stash): if key == '__dir__': return meths(key) if key[:2] == '__' == key[-2:]: raise AttributeError, key bok[key] = ans = meths(key)(obj, key) return ans lookups.insert(0, early) lookups.append(getit) # arguably insert(0, ...) __cache__ = None cache Cache: """Base-class implementing flushable lazy attribute cache.""" # we could do a lazy/cache hybrid, but I doubt its wisdom.