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