def _get_combined_method(method_list):
def new_func(*args, **kwargs):
[m(*args, **kwargs) for m in method_list]
return new_func
def component_method(func):
""" method decorator """
func._is_component_method = True
return func
class Component(object):
""" data descriptor """
_is_component = True
def __init__(self):
self._cache = {} # id(instance) -> component obj
def __get__(self, instance, owner):
if instance is None:
return self
else:
return self._cache.get(id(instance), None)
def __set__(self, instance, value):
self._cache[id(instance)] = value
self._refresh_component_methods(instance)
def __delete__(self, instance):
# delete this instance from the cache
del(self._cache[id(instance)])
self._refresh_component_methods(instance)
def _refresh_component_methods(self, instance):
icls = instance.__class__
# get all components defined in instance cls
components = []
for attr in dir(icls):
obj = getattr(icls, attr)
if getattr(obj, '_is_component', False):
comp = getattr(instance, attr, None)
if comp is not None:
components.append(comp)
# clear all of the current instance _component_methods
icms = getattr(instance, '_instance_component_methods', [])
for meth in icms:
delattr(instance, meth)
# generate new set of instance component methods
icms = {}
for c in components:
ccls = c.__class__
for attr in dir(ccls):
obj = getattr(ccls, attr)
if getattr(obj, '_is_component_method', False):
if attr not in icms:
icms[attr] = []
icms[attr].append(getattr(c, attr))
# also maintain the instance's class original functionality
for attr, meths in icms.items():
obj = getattr(icls, attr, None)
if obj is not None:
if callable(obj):
icms[attr].insert(0, getattr(instance, attr))
else:
raise ValueError("Component method overrides attribute!")
# assign the methods to the instance
for attr, meths in icms.items():
if len(meths) == 1:
setattr(instance, attr, icms[attr][0])
else:
setattr(instance, attr, _get_combined_method(meths))
# write all of the assigned methods in a list so we know which ones to
# remove later
instance._instance_component_methods = icms.keys()
if __name__ == "__main__":
class Robot(object):
firmware = Component()
arm = Component()
def power_on(self):
print 'Robot.power_on'
def kill_all_humans(self):
""" demonstrates a method that components didn't take over """
print 'Robot.kill_all_humans'
class RobotFW(object):
@component_method
def power_on(self):
print 'RobotFW.power_on'
self.power_on_checks()
def power_on_checks(self):
""" demonstrates object encapsulation of methods """
print 'RobotFW.power_on_checks'
class UpgradedRobotFW(RobotFW):
""" demonstrates inheritance of components possible """
@component_method
def laser_eyes(self, wattage):
print "UpgradedRobotFW.laser_eyes(%d)" % wattage
class RobotArm(object):
@component_method
def power_on(self):
print 'RobotArm.power_on'
@component_method
def bend_girder(self):
print 'RobotArm.bend_girder'
r = Robot()
print dir(r)
r.power_on()
print '-'*20 + '\n'
r.firmware = RobotFW()
print dir(r)
r.power_on()
print '-'*20 + '\n'
print "try to bend girder (demonstrating adding a component)"
try:
r.bend_girder()
except AttributeError:
print "Could not bend girder (I have no arms)"
print "adding an arm..."
r.arm = RobotArm()
print "try to bend girder"
r.bend_girder()
print dir(r)
print '-'*20 + '\n'
print "upgrading firmware (demonstrating inheritance)"
r.firmware = UpgradedRobotFW()
r.power_on()
r.laser_eyes(300)
del(r.firmware)
try:
r.laser_eyes(300)
except AttributeError:
print "I don't have laser eyes!"