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

kin*_*all 22 python hook factory callback

我想通过提供一个调用用户功能的界面,为我的一个模块的用户提供扩展功能的能力.例如,我想让用户在创建类的实例时获得通知的能力,并且在使用它之前有机会修改实例.

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

# in mymodule.py
def factory(cls, *args, **kwargs):
    return cls(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

然后,当我需要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
Run Code Online (Sandbox Code Playgroud)

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

myFactory.chain, mymodule.factory = mymodule.factory, myFactory
Run Code Online (Sandbox Code Playgroud)

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

我希望尽可能保持简单; 我不认为大多数应用程序实际上需要链接多个用户回调,例如(尽管使用上述模式无限链接"免费").我怀疑他们需要删除回调或指定优先级或订单.像python-callbacksPyDispatcher这样的模块在我看来就像矫枉过正,尤其是后者,但如果对使用我的模块的程序员有很大的好处,我会向他们开放.

Ign*_*ams 13

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
Run Code Online (Sandbox Code Playgroud)


kin*_*all 8

结合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
Run Code Online (Sandbox Code Playgroud)

装饰功能@delegate允许其他功能"附加"到它.

@delegate
def intfactory(num):
    return int(num)
Run Code Online (Sandbox Code Playgroud)

函数可以添加到委托中+=(并删除-=).您还可以使用装饰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
Run Code Online (Sandbox Code Playgroud)

这感觉到Pythonic吗?


aar*_*ing 6

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

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

然后在你的模块中,

import sys

def new_factory(f):
    mod = sys.modules[__name__]
    f.chain = mod.factory
    mod.factory = f
    return f
Run Code Online (Sandbox Code Playgroud)