我想通过提供一个调用用户功能的界面,为我的某个模块的用户提供扩展功能的能力。例如,我想让用户在创建类的实例时获得通知,并有机会在使用之前修改实例。
我实现它的方法是声明一个模块级工厂函数来进行实例化:
# 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这样的模块在我看来就像矫枉过正,特别是后者,但如果对使用我的模块的程序员有很大的好处,我会向他们开放。
答案 0 :(得分:12)
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
答案 1 :(得分: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
使用@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吗?
答案 2 :(得分: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