在 Python 中实现钩子或回调的首选方法是啥?

Posted

技术标签:

【中文标题】在 Python 中实现钩子或回调的首选方法是啥?【英文标题】:What's the preferred way to implement a hook or callback in Python?在 Python 中实现钩子或回调的首选方法是什么? 【发布时间】:2011-05-17 14:43:24 【问题描述】:

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

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

# 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 之类的模块在我看来有点矫枉过正,尤其是后者,但如果对使用我的模块的程序员有令人信服的好处,我愿意接受。

【问题讨论】:

使用简单的子类来扩展类有什么问题?为什么所有这些混乱的回调业务? 【参考方案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

这感觉像 Python 吗?

【讨论】:

感觉更像是一个委托,这并不完全是 Pythonic 本身,但我认为这样做没有问题。 是的,当我进一步思考这个问题时,我认为它很像 C# 委托。也许我应该把这个类命名为“delegate”。 再想一想,我真的不需要元类。我最初希望能够在不实例化类的情况下获得功能,但是我现在编写它的方式,实例化可能会更容易理解。可能还需要做更多的工作。 我已经进行了上述更改。【参考方案2】:

让aaronsterling's idea 更进一步:

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

【讨论】:

对于实例化类的特定用例来说,这是一种非常优雅的方法。【参考方案3】:

我可能会使用装饰器,以便用户可以编写。

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

然后在你的模块中,

import sys

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

【讨论】:

这是一个有趣的方法。我可以添加一个执行chain 调用的包装器,这样用户就不必编写该部分。它还很好地支持用户想要在函数定义之外的某个时间添加回调函数的用例。

以上是关于在 Python 中实现钩子或回调的首选方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中实现 char const *const names[] 的最佳方法是啥

在 java 中实现 PriorityQueue 的最佳方法是啥?

在 ncurses 中实现文本滚动的推荐方法是啥?

在 Python 中实现回调 - 将可调用引用传递给当前函数

在Python中实现阈值检测功能

Python钩子函数