Entry
Behavior injection
Jul 5th, 2000 10:03
Nathan Wallace, Hans Nowak, Snippet 305, Fred Gansevles
"""
Packages: arcane_arts
"""
""" Attr.py -- Attribute management
by Behaviour Injection
Version 0.1
Date Mon Dec 7, 2020
Author F. Gansevles <gansevle@cs.utwente.nl>
"""
class __:
""" __ - the general '__XXXattr__' class
The instance of this class registers itself in the name-space of
the caller as '__self'. It also injects the 'Class'-class into the
class-chain of the original instance.
The attributes used here are:
attrs The attribute-cache used by the AttributeClasses
to store the names or name-values of the
attributes (i.e a bacing-store for __dict__)
self The 'original' self. This is the instance that let
the AttributeClass *inject* itself into the
class-chain
base The 'original' base-class of the injected
instance.
The methods defined are:
setattr Propagate the __setattr__ call
getattr Propagate the __getattr__ call
delattr Propagate the __delattr__ call
"""
def __init__ (__, Class, self, attrs):
# __ is an instance of __ (It's confusing, I know.)
name = '_%s__self' % Class.__name__ # create '__self'
if self.__dict__.has_key (name): # re-use the __self instance
self.__dict__[name].__update (attrs)
else:
__.attrs = attrs # The attributes this whole
# thing is about.
__.base = self.__class__ # Keep the original class around
# so attribute-lookup can
# continue 'up' the chain.
__.self = self # This instance gets injected.
self.__dict__[name] = __ # Register as '__self'
self.__class__ = Class # Now 'self' is an instance of
# 'Class'
def __update (__, attrs):
# Private method
# merge the new attrs with the existing ones
if type (__.attrs) == type ({}):
__.attrs.update (attrs)
else:
__.attrs = __.attrs + attrs
def setattr (__, attr, val):
# look-up the chain for __setattr__
if hasattr (__.base, '__setattr__'):
# Ok, apply it
return __.base.__setattr__ (__.self, attr, val)
# not found, default behaviour
__.self.__dict__[attr] = val
def getattr (__, attr):
try:
# get the attribute
return __.self.__dict__[attr]
except KeyError:
# look-up the chain for __getattr__
if hasattr (__.base, '__getattr__'):
return __.base.__getattr__ (__.self, attr)
# not found, default behaviour
raise AttributeError, attr
def delattr (__, attr):
# look-up the chain for __delattr__
if hasattr (__.base, '__delattr__'):
return __.base.__delattr__ (__.self, attr)
# not found, default behaviour
try:
del __.self.__dict__[attr]
except KeyError:
raise AttributeError, 'delete non-existing instance attribute'
def Attribute (self, *attrs):
""" Only attributes listed in 'attrs' may be modified
"""
class Attribute (self.__class__):
def __setattr__ (self, attr, val):
if not attr in self.__self.attrs:
raise AttributeError, "Undeclared attribute: %s" % `attr`
self.__self.setattr (attr, val)
# inject 'Attribute' into the class-chain
__ (Attribute, self, attrs)
def Persistent (self, *attrs):
""" All attributes listed in 'attrs' may *not* be removed
"""
class Persistent (self.__class__):
def __delattr__ (self, attr):
if attr in self.__self.attrs:
if hasattr (self, attr):
raise AttributeError, "Persistent attribute: %s" % `attr`
self.__self.delattr (attr)
__ (Persistent, self, attrs)
def Readonly (self, *attrs):
""" All attributes listed in 'attrs' may *not* be changed or removed
"""
class Readonly (self.__class__):
def __setattr__ (self, attr, val):
if attr in self.__self.attrs:
if hasattr (self, attr):
raise AttributeError, "Readonly attribute: %s" % `attr`
self.__self.setattr (attr, val)
def __delattr__ (self, attr):
if attr in self.__self.attrs:
if hasattr (self, attr):
raise AttributeError, "Readonly attribute: %s" % `attr`
self.__self.delattr (attr)
# inject 'Readonly' into the class-chain
__ (Readonly, self, attrs)
# instead of defining a __delattr__ method you could also add
# a call to Persistent, i.e. 'apply (Persistent, (self,)+ attrs)'
def Typed (self, *attrs, **types):
""" All attributes listed in 'attrs' must be of the given type
"""
class Typed (self.__class__):
def __setattr__ (self, attr, val):
if self.__self.attrs.has_key (attr):
if self.__self.attrs[attr] <> type (val):
raise TypeError, "Type mismatch for %s, %s expected "\
% (`attr`, self.__self.attrs[attr])
self.__self.setattr (attr, val)
for attr in attrs:
if hasattr (self, attr):
types[attr] = type (getattr (self, attr))
__ (Typed, self, types)
def Doc (self, **attrs):
""" Add a hidden '__doc' extension to the attributes listed in 'attrs'
"""
class Doc (self.__class__):
def __getattr__ (self, attr):
if attr[-5:] == '__doc':
doc = attr[:-5]
try:
self.__self.getattr (doc)
except AttributeError:
return self.__self.getattr (attr)
try:
return self.__self.attrs [doc]
except KeyError:
return None
return self.__self.getattr (attr)
__ (Doc, self, attrs)
class Class:
""" A test-class for Attr.py
"""
__dict = None
def __init__ (self):
# I use a 'hidden' dictionary to show that, in the end, the
# Class.__XXXattr__ methods are called.
self.__dict = {}
Attribute (self, 'a', 'b', 'c')
Readonly (self, 'a')
Typed (self, 'a', b = type(''))
Persistent (self, 'b')
Doc (self, a = 'This is a read-only attribute')
self.a = 1
self.b = '2'
def __setattr__ (self, attr, val):
if self.__dict is None:
self.__dict__[attr] = val
else:
print 'Class::%s = %s' % (attr, val)
self.__dict[attr] = val
def __getattr__ (self, attr):
try:
val = self.__dict[attr]
print 'Class::%s --> %s' % (attr, val)
return val
except KeyError:
raise AttributeError, attr
def __delattr__ (self, attr):
try:
del self.__dict[attr]
print 'del Class::%s' % attr
except KeyError:
raise AttributeError, attr
c = Class ()
# try this as follows:
# python -i Attrs.py
# >>> c.x = 1 # Attribute
# >>> c.a = 2 # Readonly
# >>> c.b = 2 # Typed
# >>> del c.b # Persistent
# >>> print c.a__doc # Doc
if __name__ == "__main__":
import sys
try: c.x = 1
except: print sys.exc_value
try: c.a = 2
except: print sys.exc_value
try: c.b = 2
except: print sys.exc_value
c.b = "Hello"
try: del c.b
except: print sys.exc_value
print c.a__doc, c.b__doc