22. 编译器通行证
22. 编译器通行证
编译器将 AST 和符号表信息转换为可执行代码对象。
早期阶段回答结构性问题。```text id="lfk0j9" tokenizer: What lexical units are in the source?
parser: What syntax tree do these tokens form?
symbol table:
What scope does each name belong to?
编译器回答执行问题。text id="ju2gsy"
Which bytecode instructions should be emitted?
Which constants belong in co_consts?
Which names belong in co_names?
Which local variables belong in co_varnames?
Where should jumps go?
How large can the evaluation stack grow?
Which exception table entries are needed?
Which nested code objects must be created?
```输出是一个code目的。该代码对象可以由 CPython 评估循环执行。
22.1 在编译管道中的位置
编译器位于解析和范围分析之后。```text id="9mc7xw" source ↓ tokenization ↓ parsing ↓ AST ↓ symbol table ↓ compiler passes ↓ code object ↓ interpreter execution
在 Python 级别,`compile()`函数暴露了这个阶段:```python id="2sweqj"
src = "x = 1 + 2\n"
code = compile(src, "<input>", "exec")
print(code)
```您可以使用以下命令检查结果`dis`:
```python id="24znqy"
import dis
code = compile("x = 1 + 2\n", "<input>", "exec")
dis.dis(code)
```## 22.2 编译器的主要输入
编译器使用早期阶段的两个主要产品。
|输入 |角色 |
| ------------- | ------------------------------------------------------------------ |
|谷草转氨酶 |描述语句、表达式、运算符和源位置 |
|符号表 |描述名称的范围分类 |
AST 说:```text id="iqtzc2"
there is an assignment
target is Name("x")
value is BinOp(Constant(1), Add, Constant(2))
```符号表表示:```text id="2kd8cu"
x is local/global in this scope
```它们一起允许生成字节码。
例如,编译器在以下之间进行选择:```text id="1lrzcy"
STORE_FAST
STORE_NAME
STORE_GLOBAL
STORE_DEREF
```取决于范围信息。
## 22.3 编译器输出:代码对象
编译器生成代码对象。
代码对象包含字节码和执行元数据。
重要字段包括:```text id="6p1zp9"
co_code bytecode bytes
co_consts constants
co_names global and attribute names
co_varnames local variable names
co_freevars free variable names
co_cellvars cell variable names
co_filename source filename
co_name function or module name
co_qualname qualified name
co_firstlineno first source line
co_flags execution flags
co_stacksize required value stack size
```例子:```python id="z2p4ol"
def add(a, b):
return a + b
code = add.__code__
print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)
print(code.co_stacksize)
```代码对象是不可变的执行数据。函数对象用运行时上下文(例如全局变量、默认值、注释和闭包单元)包装代码对象。
## 22.4 编译是递归的
嵌套函数、lambda、推导式、类和生成器表达式创建嵌套代码对象。
例子:```python id="nxf9b9"
def outer(x):
def inner(y):
return x + y
return inner
```模块代码对象包含一个功能代码对象`outer`。
这`outer`代码对象包含一个函数代码对象`inner`。
从概念上讲:```text id="g7e4jo"
module code object
constants:
code object for outer
constants:
code object for inner
```在运行时,执行`def outer`从创建一个函数对象`outer`代码对象。执行中`def inner`里面`outer`创建另一个函数对象,并在需要时使用闭包单元。
## 22.5 语句编译
语句通常会发出执行操作的字节码。
例子:```python id="f8r2kh"
x = 1
```编译模式:```text id="vbnwbg"
compile right-hand expression
store result into target
```简化的字节码:```text id="ozi020"
LOAD_CONST 1
STORE_NAME x
```例子:```python id="jflq5g"
return x
```编译模式:```text id="j5nviu"
compile return expression
emit RETURN_VALUE
```简化的字节码:```text id="5k1c90"
LOAD_FAST x
RETURN_VALUE
```确切的字节码因Python版本而异,但结构思想是稳定的。
## 22.6 表达式编译
表达式发出字节码,在计算堆栈上留下一个值。
例子:```python id="thj50j"
a + b
```编译模式:```text id="f51gg5"
load a
load b
apply binary operation
```简化的字节码:```text id="esgtwo"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
```表达式语句通常会丢弃其结果:```python id="77sckz"
f()
```编译模式:```text id="u882gm"
load function
call function
pop result
```简化的字节码:```text id="fo9egq"
LOAD_NAME f
CALL
POP_TOP
```编译器维护堆栈规则。每个表达式都有一个已知的堆栈效应。
## 22.7 名称编译
名称字节码取决于范围分类。
例子:```python id="7v96c6"
def f(a):
return a
a是一个局部变量:text id="spceye" LOAD_FAST a 例子:```python id="o6h1if"
x = 1
def f(): return x
`x`从内部来看是全球性的`f`:
```text id="xmpdg1"
LOAD_GLOBAL x
```例子:```python id="yptzi2"
def outer():
x = 1
def inner():
return x
x是一个来自内部的闭包变量inner:
LOAD_DEREF x
```这就是符号表分析先于字节码发射的原因。
## 22.8 目标编译
赋值目标的编译方式与值表达式不同。
例子:```python id="xpt040"
x = value
```目标操作:```text id="jfu667"
STORE_FAST or STORE_NAME or STORE_GLOBAL or STORE_DEREF
```例子:```python id="rtgk3q"
obj.attr = value
```目标操作:```text id="r72bwa"
compile obj
compile value
STORE_ATTR attr
```例子:```python id="s5jn7u"
items[i] = value
```目标操作:```text id="yyzjb4"
compile items
compile i
compile value
STORE_SUBSCR
```相同的 AST 形状可以根据上下文进行不同的编译。
比较:```python id="i5tdmi"
obj.attr
```用作值:```text id="14wnr4"
LOAD_ATTR attr
```和:```python id="c46iv2"
obj.attr = 1
```用作目标:```text id="7uswmv"
STORE_ATTR attr
```AST 上下文,例如`Load`, `Store`, 和`Del`推动这种区别。
## 22.9 控制流编译
控制流需要跳转和标签。
例子:```python id="5u72qc"
if x:
a = 1
else:
a = 2
```编译模式:```text id="c7me6c"
compile test x
jump to else block if false
compile body
jump to end
else label:
compile else body
end label:
continue
```简化的字节码形状:```text id="ukb3dq"
LOAD_NAME x
POP_JUMP_IF_FALSE else_label
LOAD_CONST 1
STORE_NAME a
JUMP_FORWARD end_label
else_label:
LOAD_CONST 2
STORE_NAME a
end_label:
```编译器首先发出符号跳转或内部标签。稍后的汇编将它们解析为具体的字节偏移量或指令偏移量。
## 22.10 循环
循环需要入口标签、出口标签,有时还需要继续目标。
例子:```python id="28xqrv"
while x:
work()
```编译模式:```text id="aq977s"
loop_start:
compile test
jump to loop_end if false
compile body
jump to loop_start
loop_end:
```例子:```python id="u0a0kq"
for item in items:
work(item)
```编译模式:```text id="fs8o3k"
compile iterable
get iterator
loop_start:
get next item or jump to loop_end
store item
compile body
jump to loop_start
loop_end:
break跳转到循环出口。continue跳转到循环继续点。
嵌套循环需要编译器块堆栈,因此break和continue瞄准正确的封闭循环。
22.11 异常处理
异常处理编译更加复杂,因为它需要控制流元数据。
例子:python id="zbie3d" try: risky() except ValueError: recover() finally: cleanup() 编译器必须为以下内容生成字节码:```text id="7fd19n"
normal execution
exception matching
handler entry
handler cleanup
finally execution
reraising when needed
从概念上讲:```text id="9wg6kk"
protected bytecode range:
risky()
handler:
if exception matches ValueError:
recover()
finally:
cleanup()
```编译器必须保留 Python 的精确异常语义,包括`else`, `finally`, `raise`、异常链接以及异常处理程序变量的清理。
## 22.12 函数定义编译
函数定义分两层进行编译。
例子:```python id="9423bh"
def add(a, b):
return a + b
```第一层:将函数体编译为嵌套代码对象。```text id="8pamlr"
code object for add:
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE
```第2层:编译创建函数对象的外部语句。```text id="ndgkj2"
LOAD_CONST <code object add>
MAKE_FUNCTION
STORE_NAME add
```这种区别是核心的。
当编译器看到时函数体不会运行`def`。在运行时,执行`def`语句创建一个函数对象并将其绑定到函数名称。
## 22.13 默认值、注释和装饰器
函数定义可能包括默认值、注释和装饰器。
例子:```python id="06culw"
@trace
def f(x: int, y=1) -> int:
return x + y
```编译必须处理:```text id="st02n8"
default argument value y=1
parameter annotation x: int
return annotation -> int
function body code object
function object creation
decorator application
name binding
```概念运行时顺序:```text id="e9t5yf"
evaluate decorator expressions
evaluate default values
evaluate annotations according to current rules
create function object
apply decorators from bottom to top
bind final function object to name
```编译器发出实现此顺序的字节码。
## 22.14 类定义编译
类定义也分层编译。
例子:```python id="c7bkv3"
class C(Base):
x = 1
def f(self):
return self.x
```类主体成为代码对象。
运行时:```text id="s2rsuu"
evaluate base classes
create class namespace
execute class body code object in that namespace
call metaclass machinery
bind resulting class object to name
```编译器发出调用内部类构建协议的字节码。
类体是可执行代码。它可以包含任意语句:```python id="we8528"
class C:
print("building class")
x = compute()
```这些语句在类定义执行时运行。
## 22.15 关闭编译
闭包需要符号表分析和字节码生成之间的协调工作。
例子:```python id="ovqkwa"
def outer():
x = 1
def inner():
return x
return inner
```编译器必须:```text id="ovv4rt"
create a cell for x in outer
compile inner with x as a free variable
create inner function with closure tuple
load x through closure access in inner
```内部形状简化`outer`:
```text id="3irffr"
MAKE_CELL x
LOAD_CONST 1
STORE_DEREF x
LOAD_CLOSURE x
BUILD_TUPLE 1
LOAD_CONST <code object inner>
MAKE_FUNCTION closure
STORE_FAST inner
LOAD_FAST inner
RETURN_VALUE
```里面`inner`:
```text id="31h0a6"
LOAD_DEREF x
RETURN_VALUE
```确切的指令因版本而异,但闭包机制仍然存在:捕获的变量存在于单元格中。
## 22.16 理解编译
推导式编译为嵌套代码对象。
例子:```python id="5w7ihk"
values = [x * x for x in range(5)]
```从概念上讲,CPython 为理解体创建类似于隐式嵌套函数的代码。
外部代码评估可迭代对象并调用理解代码。理解代码循环并构建结果列表。
这解释了为什么理解变量不会泄漏到周围的范围中。```python id="in5tuc"
x = 100
values = [x for x in range(3)]
print(x)
```外层`x`遗迹`100`。
理解力的`x`对于理解代码对象来说是本地的。
## 22.17 生成器编译
生成器函数的编译方式与普通函数不同。
例子:```python id="esmh0i"
def gen():
yield 1
yield 2
```编译器用生成器标志标记代码对象。
当被召唤时,`gen()`不立即执行主体。它返回一个生成器对象。
当生成器前进时,主体就会执行。
编译必须发出屈服点:```text id="xk7fji"
LOAD_CONST 1
YIELD_VALUE
resume point
LOAD_CONST 2
YIELD_VALUE
resume point
RETURN_VALUE
```发电机框架可以暂停和恢复。代码对象标志告诉运行时如何构造和执行它。
## 22.18 协程和异步编译
异步函数生成协程代码对象。
例子:```python id="5d36kx"
async def fetch():
result = await client.get()
return result
```编译器将代码对象标记为协程代码。
它围绕等待表达式发出等待机制。
从概念上讲:```text id="g68r60"
call client.get()
obtain awaitable
suspend until awaitable completes
store result
return result
```异步函数、异步生成器、`async for`, 和`async with`所有这些都需要特殊的字节码模式和代码标志。
## 22.19 模式匹配编译
模式匹配有专用的编译逻辑。
例子:```python id="jz9doq"
match value:
case 0:
result = "zero"
case [x, y]:
result = x + y
```编译器必须发出代码以:```text id="1gk5a7"
evaluate subject once
try first pattern
jump to body if matched
try next pattern if failed
bind captured names only on successful match
evaluate guards when present
skip remaining cases after match
```模式匹配并不普通`if`语法或普通赋值。它有自己的匹配和绑定规则。
## 22.20 常量处理
编译器将常量存储在`co_consts`。
例子:```python id="j0h9t8"
x = 123
y = "hello"
```代码对象常量可能包括:```text id="4721fz"
None
123
"hello"
nested code objects
tuples of constants
frozensets used by optimized membership tests
```检查:```python id="qrm79o"
code = compile("x = 123\ny = 'hello'\n", "<input>", "exec")
print(code.co_consts)
```常量通过字节码指令的索引引用。
## 22.21 名称和变量表
编译器构建几个名称表。
|代码对象字段|意义|
| ----------------- | ----------------------------------------------------------- |
|`co_names`|用于全局、属性和导入的名称 |
|`co_varnames`|快速局部变量名 |
|`co_cellvars`|嵌套范围捕获的局部变量 |
|`co_freevars`|从封闭范围捕获的变量 |
例子:```python id="m1cvy7"
x = 1
def f(a):
b = len(a)
return x + b
```里面`f`:
```text id="8bgw2w"
co_varnames:
a, b
co_names:
len, x
```如果涉及倒闭,`co_cellvars`和`co_freevars`出现。
## 22.22 堆栈大小计算
CPython 字节码使用值堆栈。
编译器必须计算代码对象所需的最大堆栈深度。
例子:```python id="kt5lcp"
x = a + b * c
```可能的堆栈演变:```text id="q0x4gy"
LOAD a stack: a
LOAD b stack: a, b
LOAD c stack: a, b, c
MULTIPLY stack: a, result
ADD stack: result
STORE x stack:
```最大堆栈深度:3。
代码对象将其存储为`co_stacksize`。
解释器使用它来调整帧存储的大小。
## 22.23 行号和位置表
编译器记录字节码和源位置之间的映射。
这些映射支持:```text id="srmq4m"
tracebacks
debuggers
profilers
coverage tools
stepping behavior
error locations
```例子:```python id="st4v12"
def f():
x = 1
y = 2
return x + y
```每个字节码范围都可以与源行和列信息相关联。
现代 CPython 比旧版本跟踪更精确的位置信息,这改进了回溯和调试。
## 22.24 装配
编译器通常在最终字节码字节之前发出中间指令表示。
大会决议:```text id="ic8am2"
labels
jump targets
instruction offsets
extended arguments
line tables
exception tables
final bytecode layout
```这是必要的,因为跳转偏移量取决于指令大小,并且当偏移量需要扩展参数时指令大小可能会发生变化。
从概念上讲:```text id="nxzhpt"
emit symbolic instructions
compute instruction offsets
resolve jumps
insert extended arguments if needed
recompute if sizes changed
build final bytecode bytes
build metadata tables
create code object
```## 22.25 AST 优化
CPython 在 AST 或编译器级别执行一些优化。
示例可能包括:```text id="45a56p"
constant folding
removing unreachable assert code under optimization
simplifying literal containers for membership tests
basic expression simplifications
```例子:```python id="hspw89"
x = 1 + 2
```可以像这样编写:```python id="99zdo5"
x = 3
```检查:```python id="3ltxiz"
import dis
dis.dis(compile("x = 1 + 2\n", "<input>", "exec"))
```优化是保守的。 CPython 必须保留 Python 语义,包括副作用、异常和求值顺序。
## 22.26 评估顺序
编译器必须保留 Python 的求值顺序。
例子:```python id="0rat4c"
result = f() + g()
f()必须在之前执行g()。
例子:```python id="3cgy5k" obj.attr = value()
例子:```python id="9uqpy4"
d[key()] = value()
```编译器不能仅仅因为另一个顺序可能更快而自由地重新排序操作。
Python 的动态语义使求值顺序成为可观察行为的一部分。
## 22.27 编译期间的错误处理
有些错误是在编译期间检测到的,而不是在解析期间检测到的。
示例:```python id="uxlk51"
return 1
```在模块级别。```python id="nlyacf"
break
```在循环之外。```python id="kggjx7"
continue
```在循环之外。
解析器可以为这些语句构建节点。当上下文使它们非法时,编译器会拒绝它们。
这意味着语法形状的 AST 可能仍然无法编译。
## 22.28 用于编译的公共 API
Python 通过以下方式公开编译`compile()`。
例子:```python id="njqphs"
code = compile("x = 1\n", "<input>", "exec")
exec(code)
```模式:
|模式|意义|
| -------- | -------------------------------------- |
|`exec`|编译模块或语句序列 |
|`eval`|编译单个表达式 |
|`single`|编译交互式输入 |
示例:```python id="s246ja"
compile("x = 1", "<input>", "exec")
compile("1 + 2", "<input>", "eval")
compile("print(1)", "<input>", "single")
compile()还可以编译 AST:```python id="mcvj8n"
import ast
tree = ast.parse("x = 1\n")
code = compile(tree, "
与编译器相关的重要文件包括:```text id="jwz8yn"
Python/compile.c
Python/symtable.c
Python/ast.c
Python/ast_opt.c
Python/flowgraph.c
Python/assemble.c
Include/cpython/code.h
Lib/dis.py
Lib/test/test_compile.py
Lib/test/test_dis.py
```概念角色:
|面积 |角色 |
| -------------- | ---------------------------------- |
|`symtable.c`|范围分类|
|`compile.c`| AST 到指令编译 |
|`flowgraph.c`|控制流图处理 |
|`assemble.c`|最终字节码汇编 |
|`code.h`|代码对象定义|
|`dis.py`|字节码检查 |
|编译器测试|行为和回归保护 |
确切的文件组织可以跨版本转移,但概念管道保持稳定。
## 22.30 最小心智模型
使用这个模型:```text id="ti9m3l"
The AST gives syntax.
The symbol table gives scope.
The compiler walks the AST and emits bytecode instructions.
Nested executable constructs produce nested code objects.
Control flow becomes jumps and exception tables.
Names become local, global, or closure bytecode.
Constants and names are stored in code object tables.
Assembly resolves labels, jumps, stack size, and metadata.
The result is an immutable code object ready for execution.
```编译器阶段是 CPython 将语法和范围转换为可执行指令的地方。