繁体   English   中英

为什么不能动态地向实例添加`__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)

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

PS:使用旧式课程,它可以按预期工作。

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

双下划线方法总是在类上查找,而不是在实例上查找。 请参阅新式类的特殊方法查找

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

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

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

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

在新样式类(默认3.x )中并从2.x的 object 继承,属性拦截方法__getattr____getattribute__ 不再调用实例的 dunder 重载方法的内置操作,而是从类开始搜索。

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

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

另一个(有争议的原因)涉及优化:由于对实例的内置操作通常被非常频繁地调用,因此完全跳过实例查找并直接进入类可以为我们节省一些时间。 [来源 Lutz,学习 Python,第 5 版]


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

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


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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM