一尘不染

在Python中实现挂钩或回调的首选方法是什么?

python

我想通过提供一个调用用户函数的接口来为我的一个模块的用户提供扩展其功能的能力。例如,我想让用户能够在创建类的实例时得到通知,并有机会在使用该实例之前对其进行修改。

我实现它的方法是声明一个执行实例化的模块级工厂函数:

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

然后,当我在mymodule中需要一个类的实例时,我会做factory(cls, arg1, arg2)而不是cls(arg1, arg2)

为了扩展它,程序员将在另一个模块中编写如下函数:

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

上面的回调的安装如下所示:

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

这对我来说似乎很简单,但是我想知道作为Python程序员的您是否期望函数注册一个回调而不是通过赋值来完成,或者您是否期望其他方法。我的解决方案对您来说似乎可行,惯用且清晰吗?

我希望将其保持尽可能简单。我认为大多数应用程序实际上并不需要链接多个用户回调,例如(尽管在上述模式下,无限链接“免费”提供)。我怀疑他们是否需要删除回调或指定优先级或顺序。在我看来,像python-
callbacks
PyDispatcher之类的模块似乎过大了,尤其是后者,但是,如果使用该模块的程序员能够获得引人注目的收益,我就可以接受。


阅读 153

收藏
2021-01-20

共1个答案

一尘不染

结合了Aaron使用装饰器的想法和Ignacio的类维护维护附加回调列表的类的想法,再加上从C#借用的概念,我想到了:

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

使用修饰功能@delegate可以将其他功能“附加”到该功能。

@delegate
def intfactory(num):
    return int(num)

可以使用添加函数+=(使用删除-=)。您还可以装饰funcname.callback以添加回调函数。

@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

这听起来像Pythonic吗?

2021-01-20