While playing around with some idea’s for making PyProxy completely overridable ( and also potentially undebuggable ) I started playing around with metaclasses.
Below is from my toxic directory [ gist url ]
from collections import defaultdict
from functools import wraps
Just some preliminary basic utilities
A simple bus implementation, decoratedCalls is a dictionary of service calls, with a list
of callbacks to each service call hook.
decoratedCalls = defaultdict(list)
def call(name, *args, **kwargs):
print name, args, kwargs
if name in decoratedCalls:
for cb in decoratedCalls[name]:
cb(*args, **kwargs)
Syntactic suger to add clarity later as to what functions are being bound to what. Adding in
debug/logging hooks here could allow for easier tracing of what is called for what methods.
class Subscribe(object):
def __init__(self, name):
self.name = name
def __call__(self, f):
decoratedCalls[self.name].append(f)
return f
Here’s the actual bus decorator, very simple just wraps the decorated function with a pre and post bus calls.
class BusDecorator(object):
def __init__(self, name):
self.name = name
def __call__(self, f):
@wraps(f)
def decorator(inst, *args, **kwargs):
call("%s-pre" % self.name, inst, args, kwargs)
retval = f(inst, *args, **kwargs)
call("%s-post" % self.name, inst, retval)
return retval
return decorator
And here’s my Bus Metaclass that combines most of the above.
The ease of wrapping the target class is accomplished by cdict which is a dictionary of
every defined attribute of the target class. As you can see it’s trivial to spin
through and decorate every callable with the BusDecorator
class BusWrap(type):
def __new__(mcs, clsname, bases, cdict):
modName = cdict.get("__module__", "unknownclass")
for name in cdict.keys():
prefix = "%s.%s.%s" % ( modName, clsname, name)
if callable(cdict[name]):
cdict[name] = BusDecorator(prefix)(cdict[name])
return type.__new__(mcs, name, bases, cdict)
Now give a dirt simple class like
class Foo(object):
__metaclass__ = BusWrap
def __init__(self):
print "init'd"
def bar(self):
print "bar"
def blah(self):
print "blah"
def ich(self):
print "ich"
def ego(self):
print "lego"
def say(self, *args):
print "Saying ", args
And two service handlers to pre Foo.bar being called and after Foo.ego is called
@Subscribe("__main__.Foo.bar-pre")
def preBar(inst, *args, **kwargs):
if not hasattr(inst, "ext_info"):
inst.ext_info = "Here"
@Subscribe("__main__.Foo.ego-post")
def postEgo(inst, *args, **kwargs):
if hasattr(inst, "ext_info"):
print "Extended info is ", inst.ext_info
Our test shows….
x = Foo()
x.bar()
x.blah()
x.ich()
x.ego()
x.say("abba", "dabba")
this as output
__main__.Foo.__init__-pre (<__main__.__init__ object at 0x02637890>, (), {}) {}
init'd
__main__.Foo.__init__-post (<__main__.__init__ object at 0x02637890>, None) {}
__main__.Foo.bar-pre (<__main__.__init__ object at 0x02637890>, (), {}) {}
bar
__main__.Foo.bar-post (<__main__.__init__ object at 0x02637890>, None) {}
__main__.Foo.blah-pre (<__main__.__init__ object at 0x02637890>, (), {}) {}
blah
__main__.Foo.blah-post (<__main__.__init__ object at 0x02637890>, None) {}
__main__.Foo.ich-pre (<__main__.__init__ object at 0x02637890>, (), {}) {}
ich
__main__.Foo.ich-post (<__main__.__init__ object at 0x02637890>, None) {}
__main__.Foo.ego-pre (<__main__.__init__ object at 0x02637890>, (), {}) {}
lego
__main__.Foo.ego-post (<__main__.__init__ object at 0x02637890>, None) {}
Extended info is Here
__main__.Foo.say-pre (<__main__.__init__ object at 0x02637890>, ('abba', 'dabba'), {}) {}
Saying ('abba', 'dabba')
__main__.Foo.say-post (<__main__.__init__ object at 0x02637890>, None) {}