What's the preferred way to implement a hook or callback in Python?
Taking aaronsterling's idea a bit further:
class C(object): _oncreate = [] def __new__(cls): return reduce(lambda x, y: y(x), cls._oncreate, super(C, cls).__new__(cls)) @classmethod def oncreate(cls, func): cls._oncreate.append(func)c = C()print hasattr(c, 'spew')@C.oncreatedef spew(obj): obj.spew = 42 return objc = C()print c.spew
Combining Aaron's idea of using a decorator and Ignacio's idea of a class that maintains a list of attached callbacks, plus a concept borrowed from C#, I came up with this:
class delegate(object): def __init__(self, func): self.callbacks = [] self.basefunc = func def __iadd__(self, func): if callable(func): self.__isub__(func) self.callbacks.append(func) return self def callback(self, func): if callable(func): self.__isub__(func) self.callbacks.append(func) return func def __isub__(self, func): try: self.callbacks.remove(func) except ValueError: pass return self def __call__(self, *args, **kwargs): result = self.basefunc(*args, **kwargs) for func in self.callbacks: newresult = func(result) result = result if newresult is None else newresult return result
Decorating a function with @delegate
allows other functions to be "attached" to it.
@delegatedef intfactory(num): return int(num)
Functions can be added to the delegate with +=
(and removed with -=
). You can also decorate with funcname.callback
to add a callback function.
@intfactory.callbackdef notify(num): print "notify:", numdef increment(num): return num+1intfactory += incrementintfactory += lambda num: num * 2print intfactory(3) # outputs 8
Does this feel Pythonic?
I might use a decorator so that the user could just write.
@new_factorydef myFactory(cls, *args, **kwargs): instance = myFactory.chain(cls, *args, **kwargs) # do something with the instance here if desired return instance
Then in your module,
import sysdef new_factory(f): mod = sys.modules[__name__] f.chain = mod.factory mod.factory = f return f