为什么动态添加 `__call__` 方法到实例不起作用?

Why won't dynamically adding a `__call__` method to an instance work?

在 Python 2 和 Python 3 中,代码:

class Foo(object):
    pass

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

f(1, 2, 3)

returns 作为错误 Foo object is not callable。为什么会这样?

PS:使用旧式 classes 它按预期工作。

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

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

For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

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

例如,classes 是可调用的(这就是您生成实例的方式),但是如果 Python 在实际对象上查找 __call__ 方法 ,那么你永远不能在为它们的实例实现 __call__ 的 classes 上这样做。 ClassObject() 会变成 ClassObject.__call__() ,这会失败,因为 self 参数没有传递给未绑定的方法。所以改为使用 type(ClassObject).__call__(ClassObject),调用 instance() 转换为 type(instance).__call__(instance).

要解决此问题,您可以向 class 添加一个 __call__ 方法,该方法检查 class 上的 __call__ 属性,如果有,调用它。

在新的样式中classes(默认3.x)并且从2.x中的对象继承了属性拦截方法__getattr____getattribute__are no longer called for built-in operations 在 dunder 重载实例方法上,而是搜索从 class.

开始

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

因为 classes 是 metaclasses 的实例,并且因为 metaclasses 可能定义某些作用于 classes 的操作,跳过 class(在这种情况下可以被认为是 instance)是有道理的;您需要调用 metaclass 中定义的方法,该方法被定义为处理 classes(cls 作为第一个参数)。如果使用实例查找,查找将使用为 class (self).

的实例定义的 class 方法

另一个(有争议原因)涉及优化:由于通常调用实例上的内置操作非常 经常跳过实例查找并直接转到 class 可以节省我们一些时间。 [来源 Lutz,学习 Python,第 5 版]


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

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


我能想到的最简单的解决方法是使用 setattr:

在 class Foo 上设置属性
setattr(Foo, '__call__', lambda *args: print(args))

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