class ActiveAttributes: """Use attribute names to trigger actions rather than to simply get or set the attribute. (Similar to a property in Visual Basic, or a parameterless method in Eiffel). Such an attribute is called an 'active' attribute. It is an attribute 'a' for which methods have been supplied to return x.a and set x.a = v. The first of these is a 'get' routine and has signature aget (self). The second is a 'set' routine and has signature aset (self, value). An attribute that is not active is a 'basic' attribute and is handled by get and set handlers whose defaults are to use __dict__ as usual. Children of this class must call AttributeHandler.__init__() before calling other methods of this class, and must not override __getattr__ or __setattr__. Instead, they can define new ones and use set_basic_attribute_handler to install them. One basic use is for attribute validation. See the test routine for an example. """ def __init__ (self): "Should be called by __init__ of any child. Safe for multiple inheritance." C = self.__class__ if not hasattr(C, '_active'): C._active = {} C._access = {} C._get = ActiveAttributes.basic_get C._set = ActiveAttributes.basic_set C._del = ActiveAttributes.basic_del self.set_attribute_mode ('_active', 'r') self.set_attribute_mode ('_access', 'r') self.set_attribute_mode ('_get', 'r') self.set_attribute_mode ('_set', 'r') self.set_attribute_mode ('_del', 'r') def __getattr__ (self, name): C = self.__class__ actions = C._active.get(name, None) if actions is None: return C._get(self, name) return actions[0](self) def __setattr__ (self, name, value): C = self.__class__ if C._access.get(name,'w') == 'r': raise AttributeError, "Attribute " + name + " is read-only." actions = C._active.get(name, None) if actions is None: C._set (self, name, value) return elif actions[1] is None: raise AttributeError, "Attribute " + name + " is read-only." else: return actions[1](self, value) def __delattr__ (self, name): C = self.__class__ if C._access.get(name,'w') == 'r': raise AttributeError, "Attribute " + name + " is read-only." actions = C._active.get(name, None) if actions is None: C._del (self, name) return elif actions[2] is None: raise AttributeError, "Attribute " + name + " is read-only." else: return actions[2](self, name) def basic_del (self, name): """Fundamental attribute deleter. Do not invoke directly on private name. Evades active and access provisions of this class. Best used as an argument to set_active_attribute_handler. """ del self.__dict__[name] def basic_get (self, name): """Fundamental attribute getter. Do not invoke directly on private name. Evades active and access provisions of this class. Best used as an argument to set_active_attribute_handler. """ return self.__dict__[name] def basic_set (self, name, value): """Fundamental attribute setter. Do not invoke directly on private name. Evades active and access provisions of this class. Best used as an argument to set_active_attribute_handler. """ self.__dict__[name] = value def get_active_attribute_handler (self, name): "Get current attribute handler associated with a name." return self.__class__._active.get (name, None) def get_active_attributes (self): "Return the list of attributes that have handlers." return self.__class__._active.keys() def get_attribute_mode (self, name): "Get the mode of an attribute readonly ('r') or writeable ('w')." return self.__class__._access.get(name, 'w') def get_basic_attribute_handler (self): return (self.__class__._get, self.__class__._set, self.__class__._del) def remove_active_attribute_handler (self, name): if self.__class__._active.has_key(name): del self.__class__._active[name] else: raise ValueError, 'No active attribute handler exists for ' + name if self.__class__._access.has_key (name): del self.__class__._access[name] def set_active_attribute_handler (self, name, actg, acts, actd=None): """ Set attribute handlers for name to methods actg, acts. None for acts means read-only. basic_set, basic_get, basic_del can be used for 'normal' behavior. """ if actg is None: raise ValueError, "Must supply read routine for active attribute." self.__class__._active [name] = (actg, acts, actd) def set_attribute_mode (self, name, mode): "Set the mode of an attribute to readonly ('r') or writeable ('w')." if mode != "r" and mode != "w": raise ValueError, "Mode must be 'r' or 'w'." self.__class__._access[name] = mode def set_basic_attribute_handler (self, get, set, delete): "Set the handers for attributes that are not active." self.__class__._get = get self.__class__._set = set self.__class__._del = delete if __name__ == "__main__": class Test (ActiveAttributes): def __init__ (self, initial_shape): ActiveAttributes.__init__ (self) self.set_active_attribute_handler ('shape', Test.getShape, Test.setShape) self.set_active_attribute_handler ('alwayspositive', ActiveAttributes.basic_get, Test.validate) self.__s = initial_shape self.ro = 9 self.set_attribute_mode('ro', 'w') def getShape (self): return self.__s def setShape (self, newshape): self.__s = newshape def validate (self, value): "This gatekeeper insures alwayspositive is always positive." if value < 0: raise ValueError, "Don't mess with me, buddy." self.basic_set ('alwayspositive', value) t = Test((3,2)) t.a = 1 assert t.a == 1 assert t.shape == (3,2) t.shape = (4,4) assert t.shape == (4,4) t.alwayspositive = 5 assert t.alwayspositive == 5 try: t.alwayspositive = -1 except ValueError: pass try: del t.shape except AttributeError: pass assert t.ro == 9 assert t.get_attribute_mode('ro') == 'w' try: t.ro = 3 except AttributeError: pass try: del t.ro except AttributeError: pass print "Test passed."