30. 字节码指令
30. 字节码指令
字节码指令是 CPython 评估循环执行的操作。它们是经过解析、AST 构造、符号分析和编译后的 Python 代码的紧凑的解释器级形式。
Python 函数,例如:```python id="ya54lv" def add(a, b): return a + b
您可以使用以下命令检查该流`dis`:
```python id="7yf46p"
import dis
def add(a, b):
return a + b
dis.dis(add)
```输出取决于 Python 版本,但通常显示如下指令:```text id="ot1vx4"
LOAD_FAST
LOAD_FAST
BINARY_OP
RETURN_VALUE
```这些指令是 CPython 虚拟机的词汇表。
## 30.1 什么是字节码指令
字节码指令告诉解释器执行一个小操作。
示例:```text id="4m3y16"
load a local variable
load a constant
store into a local variable
perform binary addition
call a function
jump to another instruction
return from a frame
raise an exception
build a list
load an attribute
```指令通常由两部分组成:```text id="3pmt1a"
opcode
operand
```操作码说明要做什么。
需要时,操作数提供一个小整数参数。
例如:```text id="7i5m11"
LOAD_FAST 0
```方法:```text id="ssxj7c"
load fast local variable at slot 0
```和:```text id="po4eiq"
LOAD_CONST 1
```方法:```text id="056i57"
load constant at index 1 in the code object's constants table
```有些指令没有有意义的操作数。有些操作数的含义完全取决于操作码。
## 30.2 字节码存在于代码对象中
字节码属于代码对象。
函数对象指向代码对象:```python id="3zs8ni"
def f(x):
return x + 1
print(f.__code__)
```代码对象包含指令流和指令使用的表。
重要的代码对象数据包括:
|领域|目的|
|---|---|
|`co_code`|暴露给 Python 的字节码表示 |
|`co_consts`|使用的常量`LOAD_CONST` |
| `co_names`|全局、属性和导入操作使用的名称 |
|`co_varnames`|快速局部变量名 |
|`co_freevars`|来自外部作用域的自由变量 |
|`co_cellvars`|内部函数捕获的局部变量 |
|`co_stacksize`|最大值堆栈深度|
|`co_flags`|执行标志 |
|`co_filename`|源文件名 |
|`co_name`|代码对象名称 |
|`co_qualname`|合格名称|
|异常表|异常处理程序元数据 |
|线表|源位置元数据 |
字节码流是紧凑的,因为它不直接存储全名、常量或对象指针。它将整数索引存储到这些表中。
## 30.3 说明见表
考虑:```python id="p7suvh"
def f(x):
return x + 10
```常数`10`存储在代码对象的常量表中。
当地名称`x`存储在局部变量表中。
指令流通过索引引用它们。
从概念上讲:```text id="d913xq"
co_consts:
[None, 10]
co_varnames:
["x"]
bytecode:
LOAD_FAST 0
LOAD_CONST 1
BINARY_OP +
RETURN_VALUE
```这种设计使指令保持较小。
而不是存储字符串`"x"`在每条本地访问指令中,CPython 都会存储槽号`0`。
而不是存储对象`10`直接在指令内部,CPython 存储常量索引`1`。
## 30.4 反汇编
的`dis`模块将字节码转换为可读形式。```python id="45j36i"
import dis
def f(a, b):
c = a + b
return c
dis.dis(f)
```反汇编通常包括:```text id="zxtwgj"
source line number
bytecode offset
opcode name
operand
resolved operand meaning
jump target markers
cache entries, depending on options and version
```形状示例:```text id="pely0k"
3 0 RESUME 0
2 LOAD_FAST 0 (a)
4 LOAD_FAST 1 (b)
6 BINARY_OP 0 (+)
10 STORE_FAST 2 (c)
4 12 LOAD_FAST 2 (c)
14 RETURN_VALUE
```确切的输出因 Python 版本而异。字节码是 CPython 实现细节,而不是稳定的外部指令集。
## 30.5 指令偏移量
每条指令在字节码流中都有一个位置。
反汇编将其显示为偏移量:```text id="p6y2o6"
0 RESUME
2 LOAD_FAST
4 LOAD_FAST
6 BINARY_OP
10 STORE_FAST
```偏移量用于:```text id="qv3e7s"
jump instructions
exception tables
line number mapping
tracebacks
debuggers
profilers
coverage tools
```跳转指令可以瞄准另一个偏移量。
从概念上讲:```text id="3ew1cz"
POP_JUMP_IF_FALSE 24
```方法:```text id="c0kqvz"
if condition is false, continue execution at bytecode offset 24
```现代 CPython 的细节有所不同,但思想是相同的:字节码是一系列可寻址指令。
## 30.6 堆栈效应
每条指令都有堆栈效应。
堆栈效应描述了指令如何更改帧的值堆栈。
|说明 |堆栈之前 |堆栈之后 |
|---|---|---|
|`LOAD_CONST` | `[]` | `[constant]` |
| `LOAD_FAST` | `[]` | `[local]` |
| `STORE_FAST` | `[value]` | `[]` |
| `BINARY_OP` | `[left, right]` | `[result]` |
| `LOAD_ATTR` | `[object]` | `[attribute]` |
| `CALL` | `[callable, args...]` | `[result]` |
| `RETURN_VALUE` | `[value]`|退出框架 |
编译器必须生成具有有效堆栈规则的字节码。在每条指令中,堆栈必须包含指令期望的值。
在控制流合并点,所有传入路径必须产生兼容的堆栈形状。
## 30.7 基本加载指令
加载指令将值压入堆栈。
常见负载类别:
|说明 |意义|
|---|---|
|`LOAD_CONST`|推入一个常数`co_consts` |
| `LOAD_FAST`|从快速本地槽推送本地变量 |
|`LOAD_GLOBAL`|推送全局或内置名称 |
|`LOAD_DEREF`|推送闭包单元格值 |
|`LOAD_ATTR`|从对象推送属性 |
|`LOAD_NAME`|使用类或动态命名空间查找推送名称 |
例子:```python id="0nyz5p"
def f(x):
return x + 1
```概念字节码:```text id="zc9cpj"
LOAD_FAST x
LOAD_CONST 1
BINARY_OP +
RETURN_VALUE
LOAD_FAST从当前帧读取一个槽。LOAD_CONST从代码对象读取。
30.8 存储说明
存储指令消耗堆栈中的值并将它们放置在某个地方。
| 说明 | 意义 |
|---|---|
STORE_FAST |
存储到快速本地槽 |
STORE_GLOBAL |
存储到全局命名空间 |
STORE_NAME |
存储到当前本地命名空间 |
STORE_ATTR |
存储到对象属性 |
STORE_SUBSCR |
存储到订阅目标 |
STORE_DEREF |
储存于封闭室中 |
DELETE_FAST |
删除本地槽 |
DELETE_ATTR |
删除属性 |
DELETE_SUBSCR |
删除项目 |
例子:python id="dvy1us" def f(a, b): c = a + b return c 概念堆栈行为:```text id="env4qv"
LOAD_FAST a stack: [a]
LOAD_FAST b stack: [a, b]
BINARY_OP + stack: [result]
STORE_FAST c stack: []
`STORE_FAST`消耗结果。它不会将分配的值保留在堆栈上,除非编译器显式复制它以供其他用途。
## 30.9 局部变量指令
快速本地指令使用索引,而不是名称。
为了:```python id="dse6a4"
def f(a, b):
c = a + b
return c
```编译器分配本地槽:
|插槽|名称 |
|---:|---|
| 0 |`a`|
| 1 |`b`|
| 2 |`c`|
指令:```text id="wz4dzl"
LOAD_FAST 1
```方法:```text id="nxyj1j"
push local slot 1, which is b
```这比字典查找便宜得多。该框架以类似数组的布局存储快速局部变量。
## 30.10 常量指令
Constants are stored in`co_consts`。
例子:```python id="j8ju43"
def f():
return 123
```概念代码对象:```text id="2kqu9y"
co_consts:
[None, 123]
bytecode:
LOAD_CONST 1
RETURN_VALUE
```常量表可以包含:```text id="b0tjvm"
None
numbers
strings
bytes
tuples of constants
frozensets of constants
nested code objects
```嵌套函数和推导式通常在内部显示为嵌套代码对象`co_consts`。
## 30.11 名称说明
名称查找取决于范围。
在模块级别:```python id="axbo96"
x = 10
print(x)
```名称存在于模块字典中。
在函数内部:```python id="0nxa3s"
def f():
return x
```如果`x`不是本地的,CPython 执行全局或内置查找。
重要说明:
|说明 |典型用途|
|---|---|
|`LOAD_GLOBAL`|函数全局和内置查找 |
|`LOAD_NAME`|类体和动态命名空间查找 |
|`STORE_NAME`|模块或类命名空间分配 |
|`LOAD_FAST`|函数本地槽查找 |
|`LOAD_DEREF`|闭包变量查找 |
编译器根据符号表分析来选择指令。
## 30.12 属性说明
属性访问使用如下指令`LOAD_ATTR`和`STORE_ATTR`。```python id="vqlcss"
value = obj.x
```从概念上讲:```text id="so9d8z"
LOAD_FAST obj
LOAD_ATTR x
STORE_FAST value
```属性查找可能涉及:```text id="bq7pdp"
object type
descriptor protocol
instance dictionary
slots
class dictionary
base classes
custom __getattribute__
custom __getattr__
inline caches
```但字节码级别的堆栈效果很简单:```text id="nxg6f5"
LOAD_ATTR:
input: [object]
output: [attribute_value]
```对于作业:```python id="5m00ah"
obj.x = value
```从概念上讲:```text id="y7heq6"
LOAD_FAST value
LOAD_FAST obj
STORE_ATTR x
```确切的操作数顺序由操作码实现定义。
## 30.13 下标说明
订阅使用堆栈操作数。```python id="0323zx"
value = xs[i]
```概念字节码:```text id="kvxa2c"
LOAD_FAST xs
LOAD_FAST i
BINARY_SUBSCR
STORE_FAST value
```这`BINARY_SUBSCR`指令消耗容器和密钥,然后推送结果。
对于作业:```python id="tnoqbn"
xs[i] = value
```从概念上讲:```text id="ey4z88"
LOAD_FAST value
LOAD_FAST xs
LOAD_FAST i
STORE_SUBSCR
```这调用对象的项目分配协议。
对于删除:```python id="edq3j2"
del xs[i]
```编译器发出面向删除的订阅字节码。
## 30.14 二元运算
现代 CPython 使用`BINARY_OP`对于许多二元运算,带有描述特定运算的操作数。
Python 表达式:```python id="dgwszy"
a + b
```概念字节码:```text id="g1haxi"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
```其他操作包括:```text id="7wzr1z"
+
-
*
@
/
%
//
**
<<
>>
&
|
^
```概念上也存在就地变体:```python id="zg641m"
x += y
```这可以使用就地操作形式或尝试就地语义的操作数变体。
二元运算是动态的。`+`可能意味着整数加法、浮点加法、字符串连接、列表连接或用户定义`__add__`。
## 30.15 一元运算
一元运算消耗一个堆栈值并推送一个结果。
示例:```python id="45q3s0"
-x
+x
~x
not x
```概念字节码:```text id="0wm8tb"
LOAD_FAST x
UNARY_NEGATIVE
```堆栈效果:```text id="4h9g0l"
input: [x]
output: [-x]
```一元运算仍然使用 Python 对象语义。`-x`可能会打电话`x.__neg__()`对于用户定义的对象。
## 30.16 比较指令
比较消耗操作数并推送结果。```python id="kybtui"
a < b
```从概念上讲:```text id="lpn8y7"
LOAD_FAST a
LOAD_FAST b
COMPARE_OP <
```比较操作包括:```text id="viop49"
<
<=
==
!=
>
>=
in
not in
is
is not
exception matching
```比较可能会调用用户代码:```python id="iwk7b1"
class X:
def __lt__(self, other):
return True
```因此,即使是比较指令也可以在真值测试之前在某些协议上下文中分配、调用 Python 代码、引发异常或返回非布尔对象。
## 30.17 跳转指令
跳转指令改变指令指针。
他们实施:```text id="td78o9"
if statements
while loops
for loops
boolean short-circuiting
conditional expressions
exception flow
pattern matching branches
```例子:```python id="gtmgg4"
def f(x):
if x:
return 1
return 0
```概念字节码:```text id="gsgii8"
LOAD_FAST x
POP_JUMP_IF_FALSE else_branch
LOAD_CONST 1
RETURN_VALUE
else_branch:
LOAD_CONST 0
RETURN_VALUE
```有些跳跃是无条件的。有些测试堆栈顶部的值。有些保留了价值。有些会弹出它。
跳跃的叠加效果与其目标一样重要。
## 30.18 循环指令
循环使用跳转指令和特定于迭代器的指令。
一个`while`环形:```python id="1vfdnh"
while cond:
body()
```从概念上编译为:```text id="kbpb8k"
loop_start:
evaluate cond
if false, jump loop_end
execute body
jump loop_start
loop_end:
```一个`for`环形:```python id="5q6rsq"
for x in xs:
body(x)
```使用迭代器协议指令:```text id="x43wuw"
LOAD_FAST xs
GET_ITER
loop:
FOR_ITER end
STORE_FAST x
...
JUMP loop
end:
FOR_ITER当迭代继续时,将迭代器保留在堆栈上。
30.19 调用说明
调用是对性能最关键的字节码操作之一。
为了:python id="e7dc4b" result = f(a, b) 概念堆栈设置:```text id="a7s51h"
LOAD_FAST f
LOAD_FAST a
LOAD_FAST b
CALL 2
STORE_FAST result
通话可能针对:```text id="t77949"
Python functions
built-in functions
bound methods
classes
callable instances
C extension functions
coroutines
descriptors
```CPython 使用优化的调用约定(例如向量调用)来减少临时元组和字典的创建。
## 30.20 退货说明
返回指令退出当前帧。```python id="k8kmjw"
def f():
return 42
```概念字节码:```text id="tq86ob"
LOAD_CONST 42
RETURN_VALUE
```返回前的堆栈:```text id="5ks5b0"
[42]
RETURN_VALUE消耗返回对象并将其提供给调用者。
对于没有显式返回的函数:python id="j1sx5u" def f(): pass 编译器发出一个返回值None。
从概念上讲:```text id="x839pp" LOAD_CONST None RETURN_VALUE
引发异常也使用字节码。```python id="c411i6"
raise ValueError("bad")
```从概念上讲:```text id="2swrvt"
LOAD_GLOBAL ValueError
LOAD_CONST "bad"
CALL 1
RAISE_VARARGS 1
```raise 指令退出正常执行并进入异常传播。
它必须与:```text id="nkhmtx"
thread exception state
tracebacks
exception handlers
finally blocks
context and cause
frame unwinding
```引发指令通常不会推送正常结果。
## 30.22 异常处理说明
异常处理字节码在不同的 Python 版本中发生了显着变化。
现代 CPython 对于许多任务使用与代码对象关联的异常表,而不是旧的块堆栈操作码。
尽管如此,解释器仍然需要以下指令和元数据:```text id="3wj1o7"
entering handlers
matching exception types
binding exception variables
reraising
clearing exception state
running finally blocks
handling with-statements
```例子:```python id="7pkq92"
try:
risky()
except ValueError as exc:
recover(exc)
```编译后的代码必须描述:```text id="fxm4bb"
protected bytecode range
handler target
stack depth to restore
exception matching operation
binding of exc
cleanup of exc
```异常字节码很微妙,因为它必须保留 Python 语义,同时正确清理临时堆栈值。
## 30.23 导入说明
导入具有专用的字节码操作。```python id="gik0n1"
import math
```从概念上讲:```text id="m9nu5u"
LOAD_CONST level
LOAD_CONST fromlist
IMPORT_NAME math
STORE_NAME math
```为了:```python id="zoxgo7"
from math import sqrt
```概念操作包括:```text id="n577z3"
IMPORT_NAME math
IMPORT_FROM sqrt
STORE_NAME sqrt
```导入字节码调用导入机制。它可以执行模块代码、获取导入锁、加载缓存的字节码、运行包初始化或引发导入错误。
import 语句是可执行代码,而不是静态声明。
## 30.24 容器说明
容器文字使用构建指令。```python id="fofmnt"
xs = [a, b, c]
```从概念上讲:```text id="8ezo7g"
LOAD_FAST a
LOAD_FAST b
LOAD_FAST c
BUILD_LIST 3
STORE_FAST xs
```其他构建指令包括以下操作:```text id="xge9ra"
tuples
sets
dicts
slices
strings
lists from comprehensions
maps from key-value pairs
```对于字典文字:```python id="2pyoy0"
d = {"x": a, "y": b}
```编译器排列键和值,以便字典构建指令可以使用它们。
## 30.25 开箱说明
解包指令分解可迭代值。```python id="75qhe0"
a, b = pair
```从概念上讲:```text id="h2jtqv"
LOAD_FAST pair
UNPACK_SEQUENCE 2
STORE_FAST a
STORE_FAST b
```扩展拆包:```python id="b1ifoe"
a, *middle, z = values
```使用能够为加星标的目标生成列表的解包指令。
拆包说明必须强制执行正确的数量。如果可迭代的值太少或太多,CPython 就会引发错误。
## 30.26 关闭说明
嵌套函数需要与闭包相关的指令。
例子:```python id="ji8q8n"
def outer():
x = 10
def inner():
return x
return inner
```编译器必须安排`x`生活在细胞对象中。
相关操作包括:```text id="miqfx4"
make cell variables
load closure cells
load dereferenced values
store dereferenced values
build function with closure
```从概念上讲:```text id="xdtmaw"
outer frame:
x stored in cell
inner function:
closure points to same cell
inner bytecode:
LOAD_DEREF x
```就是这样`inner`可以访问`x`后`outer`返回。
## 30.27 函数创建说明
一个`def`语句在运行时创建一个函数对象。```python id="az4vj6"
def f(x):
return x + 1
```在模块执行时,CPython 并不简单地注册静态函数。它执行从代码对象构建函数对象的字节码。
从概念上讲:```text id="oxvnd7"
LOAD_CONST <code object f>
MAKE_FUNCTION
STORE_NAME f
```如果函数具有默认值、注释、关键字默认值或闭包单元,则在函数创建期间加载并附加这些内容。
这解释了为什么`def`可执行:```python id="vb8p68"
if debug:
def f():
return "debug"
else:
def f():
return "normal"
```只有一个分支创建和绑定`f`。
## 30.28 类创建说明
一个`class`语句也执行字节码。```python id="4c6kdt"
class C:
x = 1
```概念性高层行为:```text id="n94lef"
load build_class
load class body code object
make function for class body
load class name
call build_class
store class object
```类体本身有一个代码对象。它在准备好的命名空间中运行。完成后,元类创建实际的类对象。
这解释了为什么类体可以运行任意代码:```python id="x8cd5y"
class C:
print("building class")
x = compute()
```评估循环像其他代码一样执行该主体。
## 30.29 生成器和协程指令
生成器和协程需要暂停和恢复的指令。
例子:```python id="0q3kic"
def gen():
yield 1
yield 2
```一个`yield`指令向调用者返回一个值,同时保留帧状态。
从概念上讲:```text id="47ev86"
LOAD_CONST 1
YIELD_VALUE
resume later
LOAD_CONST 2
YIELD_VALUE
resume later
LOAD_CONST None
RETURN_VALUE
```协程使用相关机制`await`。```python id="sbeqhd"
async def f():
result = await g()
return result
```字节码必须支持:```text id="b2khca"
creating coroutine objects
awaiting awaitables
suspending execution
resuming with values
resuming with exceptions
returning final result
```## 30.30 模式匹配指令
结构模式匹配编译为专门的测试、解包、属性访问、映射检查、序列检查和分支。
例子:```python id="1rrlbw"
match value:
case [x, y]:
return x + y
case _:
return 0
```编译器发出的字节码大致如下:```text id="6oglmv"
load subject
check sequence pattern
check length
unpack values
bind x and y
execute body
otherwise try next case
```模式匹配字节码必须保留失败匹配周围的 Python 语义。来自失败替代方案的绑定不得错误地泄漏到后续成功的案例中。
## 30.31 缓存指令
现代 CPython 包含与某些字节码指令关联的内联缓存条目。
在反汇编中,您可能会看到与缓存相关的条目,具体取决于选项和版本。
这些缓存条目支持以下操作的专门化:```text id="j0ixza"
attribute access
global lookup
binary operations
method calls
function calls
subscript operations
```缓存条目不是正常的 Python 操作。它们是解释器元数据。
逻辑指令的堆栈效应仍然是重要的语义部分。
例如:```text id="8sx1vs"
LOAD_ATTR name
CACHE
CACHE
LOAD_ATTR仍然消耗一个对象并推送一个属性值。缓存条目有助于加快速度。
30.32 自适应指令
自适应字节码允许 CPython 专门化热操作。
通用操作在重复执行后可以被重写或解释为专用形式。
概念流程示例:text id="08ouu3" BINARY_OP observes int + int repeatedly ↓ specialized int-add path 专用指令必须保留相同的堆栈契约:```text id="8f1uvg"
input: [left, right]
output: [result]
这种机制可以在不改变 Python 级别语义的情况下提高性能。
## 30.33 伪指令
某些指令名称可能会出现在编译器内部或生成的元数据中,但不会作为最终字节码流中的普通运行时操作码。
伪指令可以帮助表示:```text id="5ql4dw"
abstract control-flow operations
exception handling structure
compiler intermediate forms
assembler-level markers
```在阅读 CPython 内部原理时,请区分:```text id="ccdgxn"
source-level syntax
compiler intermediate instructions
runtime bytecode instructions
inline cache entries
generated metadata
```并非操作码相关文件中的每个名称都表现得像评估循环执行的正常指令。
## 30.34 指令系列
许多指示属于家庭。
示例:```text id="36dyw4"
load family
store family
delete family
binary operation family
unary operation family
jump family
call family
import family
closure family
container-build family
exception family
```指令族可帮助您阅读反汇编。
当你看到`LOAD_*`,期望一个值被推送。
当你看到`STORE_*`,期望消耗一个值。
当你看到`JUMP_*`,预计控制流会发生变化。
当你看到`CALL`,期望可调用和参数堆栈布局很重要。
## 30.35 堆栈中立指令
有些指令不会更改逻辑 Python 值堆栈。
示例可能包括:```text id="cos45t"
RESUME
NOP
cache-related entries
some instrumentation markers
```这些指令对于执行、跟踪、专门化或解释器状态仍然很重要。
堆栈中立指令即使不压入或弹出 Python 对象,也会影响运行时行为。
例如,`RESUME`标记现代 CPython 字节码中的执行恢复点。
## 30.36 版本差异
CPython 字节码在不同版本之间发生变化。
变化可能包括:```text id="kiol94"
new opcodes
removed opcodes
combined opcodes
different call protocol
different exception handling representation
different cache layout
different jump semantics
different disassembly format
specialized instruction changes
```这就是为什么字节码应被视为特定于版本的原因。
依赖于确切字节码的代码应该声明它所针对的 Python 版本。
版本敏感工具的示例:```text id="yl9axc"
bytecode transformers
coverage tools
debuggers
decompilers
optimizers
security analyzers
teaching visualizers
profilers
```对于普通的 Python 应用程序代码,字节码详细信息通常是无关紧要的。对于 CPython 内部结构来说,它们是核心。
## 30.37 手动读取字节码
一个有用的阅读过程:```text id="4mbysd"
identify locals
identify constants
identify names
track stack effects
mark jumps
mark call sites
mark exception regions
mark return paths
```例子:```python id="9opwew"
def f(a, b):
if a > b:
return a - b
return b - a
```概念字节码:```text id="c9ffv9"
LOAD_FAST a
LOAD_FAST b
COMPARE_OP >
POP_JUMP_IF_FALSE else
LOAD_FAST a
LOAD_FAST b
BINARY_OP -
RETURN_VALUE
else:
LOAD_FAST b
LOAD_FAST a
BINARY_OP -
RETURN_VALUE
```堆栈跟踪:```text id="45f5d2"
LOAD_FAST a [a]
LOAD_FAST b [a, b]
COMPARE_OP > [a > b]
POP_JUMP... []
```两个分支都是直接返回,所以分支之后没有合并。
## 30.38 示例:列表理解
来源:```python id="k2d22v"
def f(xs):
return [x * 2 for x in xs if x > 0]
```从概念上讲,CPython 为理解创建了一个嵌套代码对象。
外部函数:```text id="4gr87o"
load comprehension code object
make function
load xs
get iterator
call comprehension function
return result
```内部理解代码:```text id="c4xj0s"
build empty list
for each x in iterator:
if x > 0:
append x * 2
return list
```这解释了为什么推导式有其自己的范围。
字节码指令流使其可见,因为嵌套代码对象出现在`co_consts`。
## 30.39 示例:关闭
来源:```python id="9gbvx8"
def outer(x):
def inner(y):
return x + y
return inner
```重要的字节码概念:```text id="5c33p4"
x becomes a cell variable in outer
x becomes a free variable in inner
outer creates inner with closure data
inner uses LOAD_DEREF to read x
```检查:```python id="x2ckc7"
def outer(x):
def inner(y):
return x + y
return inner
print(outer.__code__.co_cellvars)
inner = outer(10)
print(inner.__code__.co_freevars)
print(inner.__closure__)
```字节码指令显示闭包构造和取消引用访问。
## 30.40 示例:尝试除外
来源:```python id="g6xgtw"
def f(x):
try:
return 10 / x
except ZeroDivisionError:
return 0
```概念结构:```text id="eylcef"
protected region:
LOAD_CONST 10
LOAD_FAST x
BINARY_OP /
RETURN_VALUE
handler:
check exception matches ZeroDivisionError
LOAD_CONST 0
RETURN_VALUE
```代码对象包含异常表元数据,描述哪些字节码范围受到保护以及处理程序从哪里开始。
读取此字节码时,请检查以下两项:```text id="caeiew"
instruction stream
exception table
```异常表是可执行结构的一部分。
## 30.41 字节码和源代码行
字节码指令被映射回源位置。
该映射支持:```text id="vn48tw"
tracebacks
debuggers
coverage tools
profilers
line tracing
error messages
```单个源代码行可以编译为许多字节码指令。```python id="8bi6zl"
x = f(a) + g(b)
```概念字节码包括:```text id="n0ncx0"
load f
load a
call f
load g
load b
call g
binary add
store x
```源位置元数据使 CPython 能够报告更精确的错误和跟踪事件位置。
## 30.42 字节码和回溯
当异常发生时,回溯记录帧和相关指令/源位置。
例子:```python id="lzbl8z"
def f(x):
return 10 / x
f(0)
```失败的操作是除法字节码。 CPython 使用框架的代码对象和指令位置来报告源代码行。
因此,回溯连接到:```text id="cc3baw"
frame
code object
instruction offset
source location table
exception state
```## 30.43 字节码和优化
CPython 执行一些编译时和运行时优化。
编译时示例可能包括:```text id="3ej829"
constant handling
dead code handling in simple cases
jump simplification
stack size computation
scope resolution
literal container optimizations
```运行时示例包括:```text id="ta6h3c"
adaptive specialization
inline caches
optimized call paths
fast locals
specialized attribute access
specialized global lookup
```字节码指令流位于编译器和运行时优化器之间。它既是编译器的输出,也是解释器的输入。
## 30.44 字节码不是稳定的 API
CPython 字节码并不是作为稳定的公共虚拟机目标而设计的。
它可以在版本之间更改以支持:```text id="18txrq"
better performance
simpler interpreter implementation
new language features
better debugging information
new exception machinery
new call conventions
specialization
free-threading work
JIT experiments
```这并不意味着字节码不可用。这意味着字节码级工具必须具有版本感知能力。
为了稳定的程序行为,依赖Python语言语义。对于 CPython 内部工作,请研究确切 CPython 版本的字节码。
## 30.45 最小字节码解释器
玩具翻译器有助于展示这个想法。```python id="bo0wgp"
LOAD_CONST = "LOAD_CONST"
LOAD_FAST = "LOAD_FAST"
STORE_FAST = "STORE_FAST"
ADD = "ADD"
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 == STORE_FAST:
locals_[arg] = stack.pop()
elif op == ADD:
right = stack.pop()
left = stack.pop()
stack.append(left + right)
elif op == RETURN:
return stack.pop()
raise RuntimeError("missing RETURN")
```一个小程序:```python id="5gvw2y"
code = [
(LOAD_FAST, "a"),
(LOAD_FAST, "b"),
(ADD, None),
(STORE_FAST, "c"),
(LOAD_FAST, "c"),
(RETURN, None),
]
print(run(code, [], {"a": 2, "b": 3}))
```输出:```text id="n2zzxm"
5
```这个玩具遗漏了大部分 CPython:```text id="mrtfdz"
objects
reference counts
exceptions
calls
descriptors
classes
imports
closures
generators
coroutines
specialization
inline caches
tracing
thread state
```但它抓住了核心思想:字节码指令对帧状态和值堆栈进行操作。
## 30.46 常见误解
|误会 |正确型号 |
|---|---|
|字节码是另一种语法的Python源代码|字节码是解释器指令流|
|字节码可跨所有 Python 实现移植 | CPython 字节码是 CPython 特定的 |
|字节码跨版本稳定 | CPython 版本之间的字节码变化 |
|指令包含完整的变量名称 |许多指令包含代码对象表的索引|
|`dis`输出是完整的运行时故事 |运行时还使用帧、缓存、异常表和专门化 |
|一行源代码意味着一条指令 |一行通常编译为许多指令 |
|字节码总是直接映射到语法 |存在一些用于运行时协议机制的字节码 |
|内联缓存是Python操作|它们是解释器优化元数据 |
## 30.47 阅读策略
要理解字节码指令,请从小示例开始。
从以下开始:```python id="6w8kmn"
def f(a, b):
return a + b
```然后检查:```python id="0tvkkr"
import dis
dis.dis(f)
```然后检查:```python id="6h63o9"
print(f.__code__.co_consts)
print(f.__code__.co_varnames)
print(f.__code__.co_names)
print(f.__code__.co_stacksize)
```对于每条指令,询问:```text id="w1yd94"
What does it consume from the stack?
What does it push?
Which code object table does it reference?
Can it jump?
Can it raise?
Can it call Python code?
Can specialization change its fast path?
```该方法从简单的算术扩展到函数、闭包、导入、类、异常和推导式。
## 30.48章总结
字节码指令是CPython的可执行指令格式。它们存在于代码对象内部,并通过计算循环由帧执行。
核心模型是:```text id="rbfch8"
code object holds bytecode and metadata
frame holds execution state
bytecode instruction mutates frame state
evaluation loop dispatches instructions
stack effects define operand flow
```指令加载值、存储值、调用函数、执行操作、构建容器、分支、处理异常、创建函数和类、导入模块、挂起生成器并返回结果。
字节码是紧凑的、动态的、基于堆栈的且特定于版本的。理解它可以让您直接了解 Python 源代码如何变成 CPython 执行。