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."
syntax highlighted by Code2HTML, v. 0.9.1