31. 函数调用

函数调用是 CPython 中最重要的执行路径之一。一次调用同时连接多个系统:字节码执行、帧、参数绑定、描述符、方法、闭包、C API、引用计数、异常和返回处理。

一个简单的调用:python id="wauh11" result = f(1, 2) 在源代码级别看起来很小。在运行时,CPython 必须:```text id="rhdr0d" load the callable load the arguments choose the correct call protocol bind arguments to parameters create or initialize a frame if calling Python code execute the callee return a result or propagate an exception store the result


## 31.1 调用的含义

 Python 中,调用表达式具有以下一般形式:```python id="a7ltua"
callable_object(arguments)
```括号之前的对象必须是可调用的。

示例:```python id="pqkmhl"
f()
len(xs)
obj.method(1)
C(10)
decorator(fn)
callback(event)
```可以调用多种对象:

|可调用类型|示例|运行时行为 |
|---|---|---|
| Python 函数 |`f()`|创建或初始化 Python 框架 |
|内置功能|`len(xs)`|调用 C 实现 |
|绑定方法|`obj.method()`|调用带有绑定的函数`self`|
|班级 |`C()`|调用元类构造路径|
|可调用实例|`obj()`|通话`obj.__call__`|
| C 扩展函数 |`mod.func()`|调用本机扩展代码 |
|协程函数 |`async_fn()`|创建协程对象 |
|发电机功能|`gen()`|创建生成器对象 |
|描述符结果 |`obj.attr()`|可以在调用前绑定|

语法是统一的。运行时路径取决于可调用对象的类型。

## 31.2 调用字节码

调用编译为加载可调用对象及其参数的字节码,然后执行调用指令。

为了:```python id="xszy0u"
def g(a, b):
    return f(a, b)
```从概念上讲:```text id="gwqjm5"
LOAD_GLOBAL f
LOAD_FAST a
LOAD_FAST b
CALL 2
RETURN_VALUE
```调用之前的堆栈包含可调用对象和参数:```text id="rc6gwy"
[f, a, b]
```调用指令消耗它们并推送返回值:```text id="q4x2va"
[result]
```确切的指令顺序因 Python 版本而异。现代 CPython 多次更改了调用协议以提高性能。稳定的概念是:```text id="oc3h6f"
prepare callable and arguments
perform call
push result or raise exception
```## 31.3 调用堆栈布局

调用指令需要堆栈布局。位置调用的简化布局是:```text id="2dz11l"
callable
arg0
arg1
arg2
...
```为了:```python id="n90xfk"
f(x, y, z)
```调用之前的堆栈概念上是:```text id="0zk4e7"
[f, x, y, z]
```通话后:```text id="1ef6n3"
[result]
```关键字调用需要额外的元数据。```python id="zewq6w"
f(x, y=10)
```从概念上讲,CPython 必须表示:```text id="c35rsw"
callable = f
positional args = [x]
keyword names = ["y"]
keyword values = [10]
```现代 CPython 尝试在可能的情况下不构建临时元组和字典来表示这一点。

## 31.4 参数求值顺序

Python 按定义的顺序评估调用组件。

为了:```python id="r11rqm"
f(a(), b(), c())
```调用从左到右进行:```text id="rg0lon"
evaluate f
evaluate a()
evaluate b()
evaluate c()
call f with the three results
```如果`b()`加薪,`c()`不运行。

这很重要,因为参数表达式可能会产生副作用:```python id="9b37sl"
f(print("a"), print("b"))
```当使用堆栈作为临时值时,求值循环必须保留 Python 的源代码级顺序。

## 31.5 位置参数

一个简单的 Python 函数:```python id="55c6o4"
def add(a, b):
    return a + b
```有两个位置参数。

呼叫:```python id="wkxmb9"
add(2, 3)
```绑定:```text id="jlsbey"
a = 2
b = 3
``` CPython 中,被调用者框架使用快速本地槽。

从概念上讲:```text id="6u63lj"
callee frame localsplus:
    slot 0: a = 2
    slot 1: b = 3
```然后字节码执行:```text id="chrvpi"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE
```绑定步骤是调用开销的主要部分。

## 31.6 关键字参数

关键字参数按名称绑定。```python id="n28umo"
def area(width, height):
    return width * height

area(height=10, width=20)
```参数到达时不符合参数顺序,但被调用者槽必须正确填充:```text id="j16i8j"
width  = 20
height = 10
```CPython 必须检查:```text id="x7xbwk"
whether each keyword matches a parameter
whether the same parameter was supplied twice
whether required parameters are missing
whether unexpected keywords should be rejected or collected by **kwargs
```错误示例:```python id="rbyfsw"
area(20, width=10)
```这供应`width`两次:一次按位置,一次按关键字。

## 31.7 默认参数

默认参数存储在函数对象上,而不是在每次调用时重新创建。```python id="d2kqmt"
def f(x, step=1):
    return x + step
```函数对象存储默认值`step`

从概念上讲:```text id="zw5t1r"
function f
    code object
    globals
    defaults: (1,)
```当调用时:```python id="24ks4e"
f(10)
```CPython 填充:```text id="n9k9rw"
x = 10
step = 1
```著名的可变默认行为如下:```python id="kjt1ip"
def append_item(x, xs=[]):
    xs.append(x)
    return xs
```列表对象存储在函数默认值中并在调用之间重复使用。

## 31.8 仅关键字参数

仅关键字参数必须由关键字提供。```python id="53ae7c"
def connect(host, *, timeout, retries=3):
    ...
```有效的:```python id="kjp0p3"
connect("example.com", timeout=10)
```无效的:```python id="g6g3ha"
connect("example.com", 10)
```函数代码对象存储计数和布局元数据。在参数绑定期间,CPython 使用此元数据来决定哪些快速本地槽接收值。

从概念上讲:```text id="w1u0wk"
positional slots
keyword-only slots
*args slot, if present
**kwargs slot, if present
```## 31.9 可变参数位置参数

一个`*args`参数收集额外的位置参数。```python id="hggzqm"
def f(a, *args):
    return args
```称呼:```python id="ve43p4"
f(1, 2, 3)
```绑定:```text id="72r8al"
a = 1
args = (2, 3)
```CPython 为额外的位置参数创建一个元组。如果没有传递额外的参数,它将使用一个空元组。

`*args`从字节码的角度来看,slot 仍然是一个普通的局部变量槽。

## 31.10 可变参数关键字参数

一个`**kwargs`参数收集额外的关键字参数。```python id="842uzh"
def f(a, **kwargs):
    return kwargs
```称呼:```python id="e27hzk"
f(1, x=2, y=3)
```绑定:```text id="z7vjci"
a = 1
kwargs = {"x": 2, "y": 3}
```CPython 为不匹配的关键字参数创建一个字典。

如果该函数没有`**kwargs`,意外的关键字参数是错误。

## 31.11 加星标的调用参数

调用可以解压位置参数:```python id="2t6bo9"
args = (1, 2)
f(*args)
```CPython 必须评估`args`,迭代或将其转换为位置参数,然后将其合并到调用中。

允许多次拆包:```python id="dlam5o"
f(0, *xs, *ys)
```运行时必须保留从左到右的顺序。

从概念上讲:```text id="g86xe4"
positional arguments =
    [0] + list(xs) + list(ys)
```如果解压的对象不可迭代,CPython 会引发`TypeError`

## 31.12 双星调用参数

关键字参数可以从映射中解包:```python id="gst2dz"
kwargs = {"x": 1, "y": 2}
f(**kwargs)
```可以提供多个映射:```python id="1obqtk"
f(**a, **b)
```当以通常的方式调用 Python 函数时,CPython 必须检查键是否为字符串,并且必须检测重复的关键字分配。

例子:```python id="xha3n8"
f(x=1, **{"x": 2})
```这是一个错误,因为`x`供应两次。

## 31.13 参数绑定错误

许多调用失败发生在函数体开始之前。

示例:```python id="fou3a6"
def f(a, b):
    pass

f(1)
f(1, 2, 3)
f(a=1, c=2)
f(1, a=2)
```这些提高`TypeError`

评估循环启动调用,但调用机制执行绑定检查。

调用可能会在几个阶段失败:```text id="1fh4l4"
callable lookup fails
argument expression raises
argument unpacking fails
object is not callable
argument binding fails
callee body raises
return handling fails indirectly through cleanup
```## 31.14 Python 函数调用

对于正常的 Python 函数调用,CPython 会准备一个新的帧执行状态。```python id="u6ew7w"
def f(a, b):
    c = a + b
    return c
```称呼:```python id="ry7s81"
f(2, 3)
```从概念上讲:```text id="kdm5oh"
function object:
    code object
    globals
    defaults
    closure

call:
    bind a = 2
    bind b = 3
    allocate or initialize frame
    run evaluation loop
    return result
```新框架每次调用都指向相同的代码对象,但每次调用的本地槽都不同。

## 31.15 内置函数调用

内置函数是用C实现的。```python id="47h9j2"
len(xs)
```该调用不会为主体创建Python框架`len`。相反,CPython 调用 C 函数。

从概念上讲:```text id="v2dcxi"
LOAD_GLOBAL len
LOAD_FAST xs
CALL 1
    call C implementation of len
push result
```C 实现仍然接收 Python 对象并返回 Python 对象。

为了`len(xs)`,它可能会调用对象的长度槽并返回一个Python整数。

内置函数速度快的部分原因是它们避免了其主体的 Python 字节码执行。

## 31.16 C 扩展函数调用

C 扩展函数遵循与内置函数类似的原则。

C 扩展函数接收作为 Python 对象的参数,验证它们,执行本机工作,然后返回 Python 对象或发出异常信号。

概念C形:```c id="fugpzb"
static PyObject *
mod_func(PyObject *self, PyObject *args)
{
    int x;

    if (!PyArg_ParseTuple(args, "i", &x)) {
        return NULL;
    }

    return PyLong_FromLong(x + 1);
}
```现代扩展函数可以使用更快的调用约定,以避免将参数打包到元组中。

不管惯例如何,规则是:```text id="1edgq5"
return PyObject* on success
return NULL with exception set on failure
```评估循环检查结果。

## 31.17 矢量调用

Vectorcall  CPython 对许多可调用类型的快速调用约定。

它的目的是将参数作为 C 数组传递`PyObject *`值而不是总是构建临时元组和字典对象。

从概念上讲:```text id="y0ul2m"
old-style call:
    build args tuple
    build kwargs dict
    call function

vectorcall-style:
    pass pointer to argument array
    pass argument count
    pass keyword names separately
    call function
```这减少了热调用路径上的分配和复制。

对于这样的调用:```python id="i9fxmj"
f(a, b, c)
```参数在解释器堆栈上可能已经是连续的。 Vectorcall  CPython 将该类似数组的区域直接传递给可调用实现。

## 31.18 调用和绑定方法

方法调用需要绑定。```python id="yvl8kj"
obj.method(10)
```在语言层面,这意味着:```text id="ztwg7z"
look up method on obj
bind obj as self
call method with self and 10
```如果`method`是在类上定义的函数,通过实例访问它在概念上创建了一个绑定方法:```python id="t8uh02"
bound = obj.method
bound(10)
```绑定方法携带:```text id="qceh4k"
function
self object
```然后称其为耗材`self`自动地。

CPython 对常用方法调用进行了优化,以避免在立即调用方法时创建临时绑定方法对象。

## 31.19 描述符绑定

方法调用基于描述符协议。

存储在类上的函数是描述符。当通过实例访问时,其`__get__`行为产生一个绑定方法。

例子:```python id="bkvw5y"
class C:
    def f(self, x):
        return x

obj = C()
obj.f(10)
```从概念上讲:```text id="42zlm9"
C.__dict__["f"].__get__(obj, C)
    returns bound method
bound method called with x = 10
```这就是为什么:```python id="pmz8uo"
C.f(obj, 10)
```也有效。在这种形式下,不会通过实例属性访问发生自动实例绑定。你通过了`obj`明确地。

## 31.20 班级电话会议

调用类会创建或初始化实例。```python id="0bc8io"
obj = C(1, 2)
```类是可调用的,因为它的元类是可调用的。通常元类是`type`

概念流程:```text id="wy9ffx"
C.__call__(*args, **kwargs)
    calls C.__new__(C, *args, **kwargs)
    if result is instance of C:
        calls result.__init__(*args, **kwargs)
    returns result
```例子:```python id="j7g0io"
class C:
    def __new__(cls, value):
        obj = super().__new__(cls)
        return obj

    def __init__(self, value):
        self.value = value
```电话`C(10)`不仅仅是分配。这是一个协议,涉及`__new__`, `__init__`和元类行为。

## 31.21 可调用实例

如果对象的类型定义了,则该对象可以被调用`__call__````python id="jv1zpx"
class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return self.n + x

add10 = Adder(10)
print(add10(5))
```通话:```python id="f9obht"
add10(5)
```从概念上讲:```python id="o2fo9r"
type(add10).__call__(add10, 5)
``` CPython 级别,对象的类型通过类型槽提供调用行为。

## 31.22 装饰器和调用

装饰器在函数定义时执行。```python id="hxiutv"
@decorator
def f():
    pass
```从概念上讲:```python id="57azij"
def f():
    pass

f = decorator(f)
```装饰器调用在执行包含的模块、类或函数体时发生。

多个装饰器:```python id="ew1w3e"
@d1
@d2
def f():
    pass
```意思是:```python id="hq0xkt"
f = d1(d2(f))
```这很重要,因为装饰器是普通的调用。它们可以返回任何可调用或不可调用的对象。

## 31.23 调用和关闭

闭包调用通过单元对象携带捕获的变量。```python id="ld9q93"
def outer(x):
    def inner(y):
        return x + y
    return inner

f = outer(10)
f(5)
```功能`inner`有:```text id="bmsuz9"
code object
globals
closure tuple containing cell for x
```当调用时,它的框架可以访问`x`通过闭合单元。

从概念上讲:```text id="lojpq8"
inner frame:
    local y = 5
    free variable x -> cell -> 10
```调用路径设置普通参数并使闭包单元可见`LOAD_DEREF`

## 31.24 调用和生成器

调用生成器函数不会立即运行其函数体。```python id="1wn1lz"
def gen():
    yield 1

g = gen()
```该调用创建一个生成器对象。

函数体在生成器恢复时启动:```python id="xvobsw"
next(g)
```从概念上讲:```text id="1gh51l"
call generator function:
    create generator object with suspended frame
    return generator

next(generator):
    resume frame
    execute until yield or return
```这是与普通函数调用的一个主要区别。

## 31.25 调用和协程

调用异步函数会创建一个协程对象。```python id="z4c0rv"
async def fetch():
    return 1

coro = fetch()
```主体通常不会在调用时运行完成。它在等待或计划时运行。```python id="tmq7yx"
result = await coro
```从概念上讲:```text id="3o4iuh"
call async function:
    create coroutine object
    return coroutine

await coroutine:
    execute or resume coroutine frame
```函数调用创建执行对象。等待驱动执行。

## 31.26 递归调用

递归调用为同一代码对象创建多个活动框架。```python id="mmzccu"
def fact(n):
    if n <= 1:
        return 1
    return n * fact(n - 1)
```为了`fact(4)`:

```text id="v16tkx"
fact frame: n = 4
    fact frame: n = 3
        fact frame: n = 2
            fact frame: n = 1
```每个帧都有单独的快速局部变量和堆栈状态。

CPython 检查递归深度,以防止不受控制的递归耗尽运行时资源。

## 31.27 调用和异常

跟注可以通过返回或加注来退出。```python id="zv234y"
def f():
    raise ValueError("bad")

def g():
    return f()
```什么时候`f`引发,它不会推送正常的返回值。异常通过调用堆栈传播。

从概念上讲:```text id="p6uolo"
frame g calls frame f
frame f raises ValueError
frame f unwinds
frame g receives exception instead of return value
frame g unwinds unless it handles the exception
```如果`g`有一个处理程序:```python id="g7gg6a"
def g():
    try:
        return f()
    except ValueError:
        return 0
```异常被捕获`g`并在处理程序处恢复正常执行。

## 31.28 返回值

每个 Python 函数都会返回一个值。

如果不存在显式返回值,则返回`None````python id="46x94k"
def f():
    pass
```从概念上讲:```text id="99ydts"
LOAD_CONST None
RETURN_VALUE
```调用表达式总是期望:```text id="q8p8m0"
a returned Python object
or an exception
```没有第三种正常结果。

## 31.29 调用期间的引用所有权

调用是引用敏感的。

在调用期间,CPython 必须保持活动状态:```text id="hinb3j"
callable object
argument objects
keyword name objects
temporary bound method state
callee frame
return value
exception objects, if any
```调用指令必须在调用成功或失败后释放临时引用。

简化的调用路径:```c id="s4hvbw"
result = PyObject_Call(callable, args, kwargs);
if (result == NULL) {
    goto error;
}
push(result);
```但在此之前和之后,解释器必须正确清理参数引用和堆栈条目。

调用路径中的错误引用处理可能会泄漏参数或破坏仍在使用的对象。

## 31.30 调用可以重新进入Python

一帧内的调用可以运行任意 Python 代码。

这对于直接 Python 函数调用来说是显而易见的,对于看起来不像调用的操作也是如此。

例子:```python id="yv3kfl"
obj.x
```可以打电话`obj.__getattribute__`

例子:```python id="44ak0w"
a + b
```可以打电话`a.__add__`

例子:```python id="72yoig"
for x in xs:
    ...
```可以调用迭代器方法。

所以解释器必须假设很多操作可以重新进入Python执行。

调用堆栈可以从显式或隐式调用中增长:```text id="5p626a"
frame A
    executes LOAD_ATTR
        calls Python descriptor
            frame B
```## 31.31 方法调用优化

这种源模式很常见:```python id="8wm8xu"
obj.method(arg)
```一个幼稚的实现将:```text id="ywi46w"
load obj
load method attribute
create bound method object
load arg
call bound method
destroy bound method object
```CPython 通过识别常见的方法调用模式来优化这一点。

优化路径尽可能避免分配临时绑定方法:```text id="d28yc3"
load method function and self separately
call function with self and arguments
```这减少了分配和引用计数流量。

语言行为没有改变。优化仅改变内部调用路径。

## 31.32 调用和内联缓存

调用字节码可以使用内联缓存。

调用站点经常会重复看到相同的可调用对象:```python id="on96mz"
for x in xs:
    total += f(x)
```该源位置的调用指令可能会重复调用同一个函数`f`

CPython 可以缓存以下信息:```text id="ec11pe"
callable type
function version
argument shape
method lookup result
global lookup version
descriptor result
```如果警卫通过,口译员可以使用快速通道。

如果可调用对象发生变化,它就会恢复到通用行为。

## 31.33 召唤和专业化

专业化可以优化调用量大的代码。

例子:```python id="sky0ma"
def loop(xs):
    total = 0
    for x in xs:
        total += int(x)
    return total
```调用站点为`int(x)`如果它重复看到相同的可调用和参数形状,则可能会变得专门化。

专业化不会改变 Python 语义。它只会加速普通案件的发生。

堆栈合约仍然是:```text id="nx4fmo"
before call: callable and arguments
after call: result
or exception
```## 31.34 调用和 GIL

在传统的 CPython 中,Python 字节码在持有 GIL 的情况下执行。

在该模型中继续调用 Python 代码。

如果 C 实现选择围绕长时间运行的工作执行此操作,则对 C 代码的调用可能会释放 GIL

C 代码可能释放 GIL 的示例:```text id="99b4lz"
blocking I/O
compression
hashing
numeric kernels
database drivers
system calls
```这意味着如果被调用者是释放 GIL 的本机代码,则调用可能会暂时允许另一个 Python 线程运行。

对于纯 Python 调用,字节码执行在传统构建中仍由 GIL 进行序列化。

## 31.35 调用和内置方法

内置方法通常是 C 级描述符。

例子:```python id="lz9y2o"
xs.append(1)
```方法`append` C 中为列表对象实现。

高度优化的路径可以避免创建Python绑定方法对象并直接调用列表追加实现。

源码级调用:```python id="bx4yzs"
xs.append(1)
```因此涉及:```text id="2d6rn4"
attribute or method resolution
argument setup
C method call
mutation of list object
return None
```它比 Python 中实现的等效方法快得多,因为循环和变异逻辑在 C 中运行。

## 31.36 调用和`super`

`super()`是一个可调用且可识别描述符的对象,可更改方法解析。```python id="xskypw"
class Base:
    def f(self):
        return 1

class Child(Base):
    def f(self):
        return super().f() + 1
```通话:```python id="onpk7n"
super().f()
```涉及:```text id="s1yqxl"
create or resolve super object
perform attribute lookup using adjusted MRO position
bind method to original instance
call method
```这是普通的调用机制加上特殊的属性解析。

## 31.37 调用和属性

属性访问可能会在显式调用开始之前调用代码。```python id="utkq7j"
obj.factory()
```如果`factory`是一个属性:```python id="q5y4ib"
class C:
    @property
    def factory(self):
        return lambda: 42
```然后:```text id="7onci0"
obj.factory
    calls property getter
    returns callable

(...)
    calls returned callable
```因此源表达式包含一个隐式调用,后跟一个显式调用。

这就是 CPython 必须将属性访问视为潜在有效的原因。

## 31.38 调用和错误清理

假设此调用部分计算参数,然后失败:```python id="xwgmze"
f(g(), h())
```如果`h()`提高,然后:```text id="9lvmys"
f has been loaded
g() has returned
h() raises
the call to f never happens
temporaries must be cleaned up
exception propagates
```堆栈可能包含临时引用`f``g()`的结果。 CPython 必须在错误展开期间正确释放它们。

这是调用字节码和评估循环错误路径时要小心的原因之一。

## 31.39 检查调用

使用`dis`检查调用字节码。```python id="rgh73f"
import dis

def target(a, b):
    return a + b

def caller(x):
    return target(x, 10)

dis.dis(caller)
```检查函数元数据:```python id="i0pt0v"
print(target.__code__.co_argcount)
print(target.__code__.co_posonlyargcount)
print(target.__code__.co_kwonlyargcount)
print(target.__defaults__)
print(target.__kwdefaults__)
print(target.__code__.co_varnames)
``` Python 级别检查签名:```python id="dcrnsg"
import inspect

print(inspect.signature(target))
````inspect`视图是源代码级别且用户友好的。代码对象视图更接近 CPython 用于框架设置的视图。

## 31.40 一个最小的调用解释器

玩具解释器可以显示调用模型。```python id="47u5eu"
LOAD_CONST = "LOAD_CONST"
LOAD_FAST = "LOAD_FAST"
CALL = "CALL"
RETURN = "RETURN"

def run(code, consts, locals_):
    stack = []

    for op, arg in code:
        if op == LOAD_CONST:
            stack.append(consts[arg])

        elif op == LOAD_FAST:
            stack.append(locals_[arg])

        elif op == CALL:
            argc = arg
            args = stack[-argc:]
            del stack[-argc:]

            func = stack.pop()
            result = func(*args)
            stack.append(result)

        elif op == RETURN:
            return stack.pop()

    raise RuntimeError("missing RETURN")
```程序:```python id="lq4boa"
def add(a, b):
    return a + b

code = [
    (LOAD_CONST, 0),   # add
    (LOAD_FAST, "x"),
    (LOAD_CONST, 1),   # 10
    (CALL, 2),
    (RETURN, None),
]

print(run(code, [add, 10], {"x": 5}))
```输出:```text id="5boj0q"
15
```这省略了 Python 的实参绑定、描述符、C API、异常、框架、向量调用和引用计数。它仍然捕捉了堆栈的思想:调用消耗可调用对象和参数,然后推送结果。

## 31.41 常见误解

|误会 |正确型号 |
|---|---|
|调用总是创建一个 Python 框架 |内置函数和 C 函数不会为其主体创建 Python 框架 |
|调用异步函数会立即运行它 |它创建一个协程对象 |
|调用生成器函数一直运行到第一次yield |它创建一个生成器对象 |
|方法总是分配绑定方法对象 | CPython 通常会避免这种情况以进行立即调用 |
|关键字参数始终作为字典传递 |快速路径可以避免构建字典 |
|`def`只声明一个函数 |`def`执行并创建一个函数对象 |
|类不是函数,因此它们不可调用 |类可通过元类逻辑调用 |
|`obj()`必须是函数调用 |它可能会调用`type(obj).__call__`|

## 31.42 阅读策略

要研究调用,请从简单的源形式开始:```python id="s1he1e"
f()
f(a, b)
f(a=1)
f(*args)
f(**kwargs)
obj.method(x)
C(x)
async_fn()
gen_fn()
```对于每一个:```python id="cvm0yb"
import dis
dis.dis(function_containing_call)
```然后问:```text id="8l87gs"
How is the callable loaded?
How are arguments loaded?
Are keywords present?
Is unpacking present?
Does attribute lookup happen before the call?
Does binding happen?
Does the call create a Python frame?
Can the call return a coroutine or generator instead of running the body?
What cleanup is needed if argument evaluation fails?
```这种方法将调用语法转化为具体的解释器操作。

## 31.43 章节总结

CPython 中的函数调用是结构化的运行时操作。解释器评估可调用对象和参数,将它们排列在帧堆栈上,选择正确的调用协议,在需要时绑定参数,执行 Python 框架或本机 C 实现,然后推送返回的对象或传播异常。

中心模型是:```text id="oiwfbx"
load callable
load arguments
CALL
    bind arguments
    run Python frame or native callable
    return object or raise exception
store or use result
```调用的成本很高,因为它们跨越了许多运行时边界:堆栈布局、参数绑定、描述符绑定、框架设置、C API 约定、引用所有权、异常处理和专门化。

CPython 性能工作的大部分重点是在不改变 Python 动态语义的情况下降低调用成本。