__call__实际上如何工作?

Mar*_*nen 9 python python-internals

__call__每当您尝试调用对象时,都会调用Python的魔术方法.Cls()()因此等于Cls.__call__(Cls()).

函数是Python中的第一类对象,这意味着它们只是可调用对象(使用__call__).然而,__call__本身是一个函数,因此它也有__call__,这又都有自己的__call__,而这又都有自己的__call__.

因此Cls.__call__(Cls()),等于Cls.__call__.__call__(Cls())和再等同于Cls.__call__.__call__.__call__(Cls())等等等等.

这个无限循环如何结束?如何__call__实际执行代码?

Bri*_*anO 9

在引擎盖下,Python中的所有调用都使用相同的机制,并且几乎所有调用都在CPython实现中达到相同的C函数.无论对象是具有__call__方法,函数(本身是对象)还是内置对象的类的实例,所有调用(优化的特殊情况除外)都会到达该函数PyObject_Call.C函数从ob_type对象PyObject结构的字段中获取对象的类型,然后从类型(另一个PyObject结构)获取tp_call字段,这是一个函数指针.如果tp_call不是NULL,它会调用,同时传递给args和kwargs结构PyObject_Call.

当一个类定义一个__call__方法时,它会tp_call适当地设置该字段.

这是一篇详细解释所有这些内容的文章:Python内部:callables如何工作.它甚至列出并解释了整个PyObject_Call功能,这个功能并不是很大.如果你想在它的原生栖息地看到这个功能,它就在CPython repo 中的Objects/abstract.c中.

这个stackoverflow也是相关的问答:什么是Python中的"可调用"?.


Jon*_*ice 5

不存在实际的无限循环,因为__call__在所有这些情况下实际上并未调用(“调用”)该方法。仅当对提供方法的对象进行类似函数的调用时,才会直接调用它__call__

普通类实例化Cls(...)和常规函数调用f()是直接处理的已知情况。通常不会实际调用,因此即使在具有深度继承、元类等的复杂情况下,也可能发生__call__()有限数量的方法调用。__call__

因为对于概念上的无限循环的短路是否真的发生存在一些争议,所以让我们看一下反汇编的字节码。考虑以下代码:

def f(x):
    return x + 1

class Adder(object):
    def something(self, x):
        return x + 19
    def __call__(self, x):
        return x + 1

def lotsacalls(y):
    u = f(1)
    a = Adder()
    z = u + a.something(y)
    return a(z * 10)
Run Code Online (Sandbox Code Playgroud)

抱歉,这有点复杂,因为我想展示短路的几个实例——即普通def函数、__init__调用、普通方法和__call__特殊方法。现在:

带注释的反汇编

因此,在很多时候,如果 Python 真的、真正“遍历树”的概念__call__调用,它会引用Function(可能还有Method类,并调用它们的__call__方法)。事实并非如此。它在所有情况下都使用简单的字节码CALL_FUNCTION,从而缩短了概念树的遍历过程。从逻辑上讲,您可以想象有一个类Function,它有一个__call__方法,当调用函数(即该类的实例Function)时会调用该方法。但事实并非如此。编译器、字节码解释器和 C 语言基础的其他部分实际上并不遍历元类树。他们疯狂地短路。