31. 函数调用
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 动态语义的情况下降低调用成本。