通过class属性调用的函数对象失败

时间:2017-06-08 18:42:57

标签: python python-2.7

我目前有一个注册回调的类系统,然后在满足某些条件时调用它们。但是我遇到了存储函数对象的一些问题。

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]()

为什么第一个版本在第二个版本不起作用时不起作用?将其添加到列表时,有哪些功能不同。

5 个答案:

答案 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"

的说明中
  

当引用的实例属性不是数据属性时,将搜索其类。如果名称表示作为函数对象的有效类属性,则通过打包(指向)实例对象和刚在抽象对象中找到的函数对象来创建方法对象:这是方法对象。

"Random Remarks"

  

函数定义不必在文本中包含在类定义中:将函数对象赋值给类中的局部变量也是可以的。

后面引用的是我所指的例子。

基本上,我们的想法是在类定义中命名的任何函数都成为该类的方法。由于您没有实例化您的类,因此您的方法永远不会绑定到实例对象(因此... unbound method Bar() ...)。始终使用第一个参数调用方法,第一个参数包含调用它们的对象,无论您是否喜欢它。

这就是你需要为@classmethod设置特殊装饰器的原因。装饰器包装您创建的函数以为其提供所需的接口,但将类对象而不是实例对象传递给您定义的包装函数。

由于您的第二个版本创建_callback作为数据对象,它只是将一个普通的函数指针存储在列表中并按原样调用它。

如果您尝试将None传递给Foo._callback(None),您将摆脱原来的错误,但当然会遇到传递参数的问题功能没有预料到。你可以通过给Bar一个参数(在这种情况下你会忽略)解决这个问题:

def Bar(self):
    print "Called"
相关问题