为什么不动态地将`__call__`方法添加到实例工作中?

时间:2015-11-20 10:22:12

标签: python instance

在Python 2和Python 3中代码:

class Foo(object):
    pass

f = Foo()
f.__call__ = lambda *args : args

f(1, 2, 3)

返回错误Foo object is not callable。为什么会这样?

PS:对于旧式的类,它按预期工作。

PPS:此行为是有意的(请参阅接受的答案)。作为解决方法,可以在类级别定义__call__,只转发给另一个成员,并将此“普通”成员设置为每个实例__call__实现。

2 个答案:

答案 0 :(得分:19)

双重下划线方法总是在类上查找,而不是实例。见Special method lookup for new-style classes

  

对于新式类,只保证在对象的类型上定义特殊方法的隐式调用才能正常工作,而不是在对象的实例字典中。

那是因为类型可能需要支持相同的操作(在这种情况下,会在元类型上查找特殊方法)。

例如,类是可调用的(这就是你生成实例的方式),但是如果Python在实际对象上查找__call__方法,那么你就永远不会在为其实例实现__call__的类上执行此操作。 ClassObject()将成为ClassObject.__call__(),因为self参数未传递给未绑定方法,因此会失败。因此,使用了type(ClassObject).__call__(ClassObject),并且调用instance()会转换为type(instance).__call__(instance)

要解决此问题,您可以在类中添加__call__方法,该类检查类的__call__属性,如果存在,则调用它。

答案 1 :(得分:3)

在新样式类(默认3.x)中继承自2.x中的对象的属性拦截方法__getattr____getattribute__ are no longer called for built-in operations上的dunder重载方法实例,而不是搜索从类开始。

这背后的基本原理涉及由MetaClasses的存在引入的技术性。

因为类是元类的实例,并且因为元类可能定义了对类起作用的某些操作,所以跳过类(在本例中可以认为是instance)是有意义的;您需要调用元类中定义的方法,该方法被定义为处理类(cls作为第一个参数)。如果使用实例查找,查找将使用为该类(self)的实例定义的类方法。

另一个(有争议的原因)涉及优化:由于实例上的内置操作通常经常被调用非常,所以跳过实例外观 - 一起完成并直接上课节省了一些时间。 [ Source Lutz,学习Python,第5版]

主要区域这可能带来的不便是创建重载__getattr____getattribute__的代理对象,意图将调用转发给嵌入的内部对象。由于内置调用将完全跳过实例,因此它们不会被捕获,因为效果不会转发到嵌入对象。

一个简单但乏味的解决方法是实际重载你希望在代理对象中拦截的所有dunders。然后在这些重载的dunders中,您可以根据需要将调用委托给内部对象。

我能想到的最简单的解决方法是使用Foo在类setattr上设置属性:

setattr(Foo, '__call__', lambda *args: print(args))

f(1, 2, 3)
(<__main__.Foo object at 0x7f40640faa90>, 1, 2, 3)