在 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 的最佳方法是啥?