What's the preferred way to implement a hook or callback in Python? What's the preferred way to implement a hook or callback in Python? python python

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