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) {}