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

26 投票
3 回答
27378 浏览
提问于 2025-04-16 07:48

我想给我的一个模块的用户提供一个功能,让他们可以通过一个接口来调用他们自己的函数。举个例子,我希望用户能在一个类的实例被创建时收到通知,并有机会在这个实例被使用之前对它进行修改。

我实现这个功能的方式是声明一个模块级的工厂函数,用来创建实例:

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

然后当我需要在我的模块中创建一个类的实例时,我会用 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-callbacksPyDispatcher 这样的模块对我来说似乎有些过于复杂,尤其是后者,但如果对使用我模块的程序员有明显的好处,我是开放接受的。

3 个回答

6

我可能会使用一个装饰器,这样用户就可以直接写。

@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
14

进一步探讨一下aaronsterling的想法

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
13

结合了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的风格吗?

撰写回答