开发者

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

I'd like to provide the capability for users of one of my modules to extend its capabilities by providing an interface to call a user's function. For example, I want to give users the capability to be notified when an instance of a class is created and given the opportunity to modify the instance before it is used.

The way I've implemented it is to declare a module-level factory function that does the instantiation:

# in mymodule.py
def factory(cls, *args, **kwargs):
    return cls(*args, **kwargs)

Then when I need an instance of a class in mymodule, I do factory(cls, arg1, arg2) rather than cls(arg1, arg2).

To extend it, a programmer would write in another module a function like this:

def myFactory(cls, *args, **kwargs):
    instance = myFactory.chain(cls, *args, **kwargs)
    # do something with the instance here if desired
    return instance

Installation of the above callback looks like this:

myFactory.chain, mymodule.factory = mymodule.factory, myFactory

This seems straightforward enough to me, but I was wondering if you, as a Python programmer, would expect a function to register a callback rather than doing it with an assignment, or if there were other methods you would expect. Does my solution seem workable, idiomatic, and clear to you?

I am looking to keep it as simple as possible; I don't think most applications will actually need to chain more t开发者_如何学Pythonhan one user callback, for example (though unlimited chaining comes "for free" with the above pattern). I doubt they will need to remove callbacks or specify priorities or order. Modules like python-callbacks or PyDispatcher seem to me like overkill, especially the latter, but if there are compelling benefits to a programmer working with my module, I'm open to them.


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.oncreate
def spew(obj):
  obj.spew = 42
  return obj

c = 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.

@delegate
def 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.callback
def notify(num):
    print "notify:", num

def increment(num):
    return num+1

intfactory += increment
intfactory += lambda num: num * 2

print intfactory(3)   # outputs 8

Does this feel Pythonic?


I might use a decorator so that the user could just write.

@new_factory
def 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 sys

def new_factory(f):
    mod = sys.modules[__name__]
    f.chain = mod.factory
    mod.factory = f
    return f
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜