我目前有一个注册回调的类系统,然后在满足某些条件时调用它们。但是我遇到了存储函数对象的一些问题。
答
class Foo(object):
_callback = None
@classmethod
def Register(cls, fn):
cls._callback = fn
def Bar():
print "Called"
Foo.Register(Bar)
Foo._callback()
input clear Python 2.7.10 (default, Jul 14 2015, 19:46:27) [GCC 4.8.2] on linux
Traceback (most recent call last): File "python", line 12, in <module>
TypeError: unbound method Bar() must be called with Foo instance as first argument (got nothing instead)
当函数不是Foo的成员时,我不确定它为什么需要Foo实例。
B:
class Foo(object):
_callback = []
@classmethod
def Register(cls, fn):
cls._callback.append(fn)
def Bar():
print "Called"
Foo.Register(Bar)
Foo._callback[0]()
为什么第一个版本在第二个版本不起作用时不起作用?将其添加到列表时,有哪些功能不同。
答案 0 :(得分:4)
每当将一个函数赋给一个类对象时,它就变成了一个方法:
In [6]: Foo.baz = lambda self: 'baz'
In [7]: f = Foo()
In [8]: f.baz()
Out[8]: 'baz'
注意,这意味着您可以动态地向类添加方法,这些方法将在实例化对象上显示!
In [9]: Foo.anothermethod = lambda self, x: x*2
In [10]: f.anothermethod(4)
Out[10]: 8
来自docs:
如果你仍然不明白方法是如何工作的,那么看看 实施也许可以澄清问题。当一个实例属性 引用的不是数据属性,搜索其类。如果 name表示一个有效的class属性,它是一个函数对象,a 方法对象是通过打包(指向)实例对象来创建的 和一个在抽象对象中一起找到的函数对象: 这是方法对象。用方法调用方法对象时 参数列表,从实例构造一个新的参数列表 对象和参数列表,以及调用函数对象 这个新的参数列表。
答案 1 :(得分:4)
当您将Bar
分配给cls._callback
时,_callback
会自动成为Foo
的未绑定方法(因为它是一个函数)。
因此,在调用时,它应该将实例作为其第一个参数,导致在没有传递任何内容(期望实例)或传递实例(Bar
支持0个参数)的情况下失败。 / p>
但是,如果您将Bar
添加到列表中,然后访问列表元素,那么您可以使用正常旧的Bar
,而不会受到任何限制性约定的影响 - 因为它仅仅作为一个对象而不是一种约束方法被保存。
答案 2 :(得分:3)
。分配它只是将它添加到类__dict__
,它存储绑定和未绑定方法(即函数)的引用。因此,通过执行此操作,您只需添加对未绑定函数的另一个引用,该函数期望将实例作为其第一个参数。
这是一个更简单的例子:
class Foo:
test = lambda x: x
Foo.test("Hello world!")
错误:
unbound method <lambda>(): must be called....
答案 3 :(得分:3)
你还有你的功能对象。但是,每次通过类访问时,从类中调用该函数都会将其转换为未绑定的方法:
请注意,从函数对象转换为(未绑定或 每次从中检索属性时都会发生方法对象 班级或实例。
(以上内容见Python 2 data model documentation的用户定义方法)
因此,现在要求通过类调用的函数传递一个实例。
请注意,未绑定的方法仅存在于Python 2中,因此您的代码有效并且可以在Python 3中使用。
您可以通过访问其im_func
属性
Foo._callback.im_func()
# Called
第二种情况将您的函数对象存储在容器中,它与该类没有直接的业务关系,可以通过索引进行检索,并且通常称为 。
答案 4 :(得分:2)
documentation实际上提供了一个非常类似于你的例子。在"Method Objects":
的说明中当引用的实例属性不是数据属性时,将搜索其类。如果名称表示作为函数对象的有效类属性,则通过打包(指向)实例对象和刚在抽象对象中找到的函数对象来创建方法对象:这是方法对象。
函数定义不必在文本中包含在类定义中:将函数对象赋值给类中的局部变量也是可以的。
后面引用的是我所指的例子。
基本上,我们的想法是在类定义中命名的任何函数都成为该类的方法。由于您没有实例化您的类,因此您的方法永远不会绑定到实例对象(因此... unbound method Bar() ...
)。始终使用第一个参数调用方法,第一个参数包含调用它们的对象,无论您是否喜欢它。
这就是你需要为@classmethod
设置特殊装饰器的原因。装饰器包装您创建的函数以为其提供所需的接口,但将类对象而不是实例对象传递给您定义的包装函数。
由于您的第二个版本创建_callback
作为数据对象,它只是将一个普通的函数指针存储在列表中并按原样调用它。
如果您尝试将None
传递给Foo._callback(None)
,您将摆脱原来的错误,但当然会遇到传递参数的问题功能没有预料到。你可以通过给Bar
一个参数(在这种情况下你会忽略)解决这个问题:
def Bar(self):
print "Called"