Python 呼叫实际上是如何工作的?

Python 呼叫实际上是如何工作的?,python,python-internals,Python,Python Internals,每当您试图调用一个对象时,就会调用Python的神奇方法\uuuuu call\uuuuCls()等于Cls.\uu调用(Cls()) 函数是Python中的第一类对象,这意味着它们只是可调用的对象(使用\uuuu call\uuu)。然而,\uuuuu call\uuuuu本身就是一个函数,因此它也有\uuuu call\uuuuu,它也有自己的\uuuuu call\uuuuu 因此Cls.\uuu-call(Cls())因此等于Cls.\uu-call(Cls()),再次与Cls.\uu-

每当您试图调用一个对象时,就会调用Python的神奇方法
\uuuuu call\uuuu
<因此,code>Cls()等于
Cls.\uu调用(Cls())

函数是Python中的第一类对象,这意味着它们只是可调用的对象(使用
\uuuu call\uuu
)。然而,
\uuuuu call\uuuuu
本身就是一个函数,因此它也有
\uuuu call\uuuuu
,它也有自己的
\uuuuu call\uuuuu

因此
Cls.\uuu-call(Cls())
因此等于
Cls.\uu-call(Cls())
,再次与
Cls.\uu-call(Cls())
相关


这个无限循环是如何结束的?
\uuuu call\uuuu
实际上是如何执行代码的?

没有实际的无限循环,因为对于所有这些情况,
\uuu call\uuuuu
方法实际上并没有被调用(“调用”)。只有在对象上有类似于调用的函数提供了
\uuu调用\uu
方法时,才会直接调用它

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

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

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)
抱歉,这有点复杂,因为我想展示几个短路实例——即,普通
def
函数、
\uuuuu init\uuuuu
调用、普通方法和
\uu调用
特殊方法。现在:


因此,如果Python真的真的在概念性的调用树上“行走”,那么这里有一系列的时候,它会引用
函数
(可能还有
方法
类,并调用它们的
调用
方法)。没有。它在所有情况下都使用简单的字节码
CALL_函数
,使概念树向下走短路。从逻辑上讲,您可以想象有一个类
函数
,它有一个
\u调用
方法,在调用函数(即
函数
类的实例)时调用该方法。但事实并非如此。编译器、字节码解释器和C语言基础的其他部分实际上并不遍历元类树。他们疯狂地短路。

我没有检查任何文档,但从我的测试来看,它似乎并不总是被调用:

def func1(*args, **kargs):
    print "func1 called", args, kargs

def func2(*args, **kargs):
    print "func2 called", args, kargs

func1.__call__ = func2

func1() # here is still called func1

class Cls:
    def __init__(*args, **kargs):
        print "init called", args, kargs
    def __call__(*args, **kargs):
        print "object called", args, kargs

obj = Cls() # here is actually called __init__
obj()  # here is called __call__
这张照片

func1 called () {}
init called (<__main__.Cls instance at 0x0000000002A5ED88>,) {}
object called (<__main__.Cls instance at 0x0000000002A5ED88>,) {}
func1调用(){}
init调用(,){}
对象名为(,){}

在后台,Python中的所有调用都使用相同的机制,并且在CPython实现中几乎所有调用都到达相同的C函数。无论对象是具有
\uuuu call\uuuu
方法的类的实例、函数(本身是对象)还是内置对象,所有调用(优化的特殊情况除外)都会到达函数
PyObject\u call
。该C函数从对象的
PyObject
struct的
ob\u type
字段获取对象的类型,然后从类型(另一个
PyObject
struct)获取
tp\u调用
字段,它是一个函数指针。如果
tp_调用
不是
NULL
,则它通过该调用调用,并将args和kwargs结构传递给
PyObject_调用

当一个类定义了一个
\uuuuuuuuuuuuuuuuu
方法时,它会适当地设置
tp\u call
字段

下面有一篇文章详细解释了这一切:。它甚至列出并解释了整个
PyObject\u调用
函数,这个函数不是很大。如果你想在它的原生栖息地看到它的功能,可以在CPython报告中看到


与此相关的还有stackoverflow Q&A:。

,但函数本身只是“提供
\uuuu调用\uuu方法的对象”,包括
\uu调用\uuu
本身。因此,要调用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,您将调用它的
\uuuuuuuu调用
。这条线走到哪里去了?它是基于什么的?不,它们实际上不是。从概念上讲,它们可以被视为“只是提供调用方法的对象”,但实际上它们是由编译器和运行时直接理解并直接实现的。就像斐波那契数列一样。是的,有一个完全递归的定义和可能的实现。但大多数实际实现都是迭代的。他们缩短了逻辑递归,这样就不必费力地经历漫长的递归链。我不是说你错了,因为我不确定,但你能证明吗?对我来说,它们似乎是普通的对象,BrianO的答案与源代码的链接似乎都支持我的理论。我并不是说我是对的,而你是错的,但对我来说,它们现在看起来只是对象,如果定义了一个,C实现只调用一次
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。我添加了带注释的反汇编程序输出,它显示了几乎所有您想象的在某些元类上调用
\uuuu call\uuuu
的情况实际上并不存在。编译器和字节码引擎没有深入遍历任何类层次结构或元类树。这并不是说没有涉及更多元对象的情况(例如,当提供纯Python元类时,或者当存在更深入的继承结构时)。但是在所有典型的情况下,短路控制着陆地。对于
obj=Cls()
,请注意
Cls
只是
type
的一个实例,因此这将等同于
type.\uu调用