CPython 字节码反汇编完全指南:深入解析 `dis` 模块、命令行工具与字节码指令集
本指南以 CPython 仓库官方文档 Doc/library/dis.rst 为骨架,结合 Lib/dis.py、Lib/opcode.py 的源码实现,系统讲解 Python 标准库 dis 模块:如何反汇编函数、代码对象与 traceback,如何通过命令行工具与 Bytecode/Instruction API 提取字节码细节,以及从通用指令到伪指令、从 opcode 集合到专有操作数的完整语义。读完本文,你将具备用字节码视角分析、调试、乃至理解 CPython 解释器(含 inline cache、自适应特化与 JIT 入口)的能力。
适用范围说明:本文基于当前 CPython main 分支源码撰写,其中已包含 3.11 引入的 inline cache(
CACHE)与自适应字节码、3.12 引入的异常处理重构、3.13 引入的逻辑标签与show_offsets、3.14 引入的show_positions、-S/--specialized选项以及尚未正式定版的新特性(文档中以versionchanged:: next标注,如show_jit)。字节码是 CPython 解释器的实现细节,官方不保证其跨 Python VM 或跨版本稳定,请以你所使用的解释器实际输出为准。
一、dis 模块定位:解读 CPython 字节码的窗口
dis 模块(标准库 Lib/dis.py)支持对 CPython 字节码(bytecode)进行分析——其方式正是反汇编(disassembling):把编译后的二进制字节码还原为可读的助记符指令序列。这些字节码在仓库中被定义于 Include/opcode.h,并同时被编译器(Python 前端将 AST 编译为字节码)和解释器(虚拟机执行字节码)使用;dis 模块本身则是站在两者之间的观察者。
由于字节码是解释器的实现细节,官方文档明确提示:
- 字节码可能在 Python 各版本之间被新增、删除或修改,使用
dis的代码不应期望跨 VM 或跨版本通用; - 版本演进本身在文档中留下了清晰的轨迹(见
Doc/library/dis.rst开头的 impl-detail 列表):- 3.6:每条指令改用固定 2 字节表示(此前随指令变化);
- 3.10:跳转、异常处理与循环类指令的参数从“字节偏移”改为“指令偏移”;
- 3.11:部分指令伴随一个或多个内联缓存条目,表现形式为
CACHE指令,默认隐藏、可用show_caches=True显示;解释器开始对字节码做自适应特化(adaptive specialization),可用adaptive=True展示; - 3.12:跳转参数变为“相对紧邻该跳转指令的
CACHE条目之后那条指令的偏移”,因此CACHE的存在对前向跳转透明,但分析后向跳转时必须计入; - 3.13:输出对跳转目标与异常处理器改用逻辑标签而非指令偏移;新增命令行
-O选项与show_offsets参数; - 3.14:新增命令行
-P(show_positions,显示指令在源码中的位置)与-S(显示特化字节码)。
二、快速上手:反汇编一个函数
文档以如下函数为例(也是 Lib/dis.py doctest 中反复出现的对象):
def myfunc(alist):
return len(alist)
在解释器中执行:
>>> import dis
>>> dis.dis(myfunc)
2 RESUME 0
3 LOAD_GLOBAL 1 (len + NULL)
LOAD_FAST_BORROW 0 (alist)
CALL 1
RETURN_VALUE
输出说明(本文所基于的 main 分支在 3.13+ 行为之上):
- 行首的数字 2、3 是源码行号;
RESUME是每个函数入口的“无操作但执行内部追踪/调试/优化检查”的指令;LOAD_GLOBAL 1 (len + NULL)中参数是co_names的索引,(len + NULL)是反汇编器对参数的解释——+ NULL表示后续调用需要一个NULL占位(3.11+ 的调用约定);LOAD_FAST_BORROW以借用引用方式压入局部变量alist(3.14 新增的专用快速指令);CALL 1表示调用栈顶可调用对象并传入 1 个位置参数;RETURN_VALUE将栈顶作为返回值返回给调用者。
注意:由于文档为 reST/doctest 编写时代反汇编结果可能随优化不断变化,你本机输出的指令名与参数可能与上例略有差异,这是正常的实现细节漂移。
三、命令行接口:把文件或 stdin 反汇编到终端
dis 模块可以脚本方式直接调用:
python -m dis [-h] [-C] [-O] [-P] [-S] [infile]
各选项含义(对应 Lib/dis.py 中 main() 的 argparse 定义):
| 选项 | 长选项 | 含义 | 引入版本 |
|---|---|---|---|
-h |
--help |
显示用法并退出 | — |
-C |
--show-caches |
显示内联缓存(CACHE)条目 |
3.13 |
-O |
--show-offsets |
显示每条指令的偏移 | 3.13 |
-P |
--show-positions |
显示指令在源码中的行列位置 | 3.14 |
-S |
--specialized |
显示特化(自适应优化后)的字节码 | 3.14 |
行为约定:
- 若给出
infile,将其反汇编后的代码写到 stdout; - 否则从 stdin 读取编译源码再反汇编。
源码中 main() 的取值逻辑(Lib/dis.py):infile 缺省为 '-'(此时读 sys.stdin.buffer),随后对读到的源码以 exec 模式 compile 成代码对象,再调用 dis(code, show_caches=..., adaptive=args.specialized, show_offsets=..., show_positions=...)。也就是说,-S 在实现上映射为 dis() 的 adaptive 参数。
$ python -m dis -O -P -C sample.py # 同时显示指令偏移、源码位置与内联缓存
四、Bytecode 字节码分析类
Bytecode(3.4 加入)是字节码分析的便捷封装:它可以把函数、生成器、异步生成器、协程、方法、源码字符串或 compile() 返回的代码对象包装成可迭代对象,迭代时逐个产出 Instruction 实例——其内部核心正是 get_instructions。
>>> import dis
>>> bytecode = dis.Bytecode(myfunc)
>>> for instr in bytecode:
... print(instr.opname)
...
RESUME
LOAD_GLOBAL
LOAD_FAST_BORROW
CALL
RETURN_VALUE
构造函数签名:
Bytecode(x, *, first_line=None, current_offset=None,
show_caches=False, adaptive=False, show_offsets=False,
show_positions=False, show_jit=False)
各参数语义(对照 Lib/dis.py 中 __init__ 的实现):
| 参数 | 含义 |
|---|---|
x |
函数/生成器/异步生成器/协程/方法/源码字符串/代码对象 |
first_line |
若给出,指定反汇编输出中第一条源码行对应的行号;否则直接取自代码对象的源码行信息 |
current_offset |
若给出,dis() 输出会对该偏移处的指令显示“当前指令”标记(-->) |
show_caches |
为 True 时显示解释器用于特化字节码的内联缓存条目 |
adaptive |
为 True 时显示可能与原始字节码不同的特化字节码 |
show_offsets |
为 True 时在输出中包含指令偏移 |
show_positions |
为 True 时在输出中包含指令的源码位置 |
show_jit |
为 True 时显示 ENTER_EXECUTOR 指令(JIT 入口点,默认隐藏,versionchanged:: next 新增) |
类成员与方法:
codeobj:编译后的代码对象(源码在__init__里通过_get_code_object(x)解析得到);first_line:代码对象的第一条源码行(如可用);dis():返回格式化视图——与dis.dis()打印内容相同,但以多行字符串形式返回;内部通过io.StringIO捕获_disassemble_bytes的输出(Lib/dis.py),current_offset存在时对应指令标出-->;info():返回与code_info()相同的、关于代码对象的详细格式化多行字符串;from_traceback(tb, *, show_caches=False):类方法,从给定 traceback 构造Bytecode实例,并把current_offset设为引发异常的指令;实现会先沿着tb_next走到最内层帧(Lib/dis.py)。这是定位“到底哪条字节码炸了”的利器。
版本轨迹:3.7 起可处理协程与异步生成器对象;3.11 增加 show_caches/adaptive;3.13 增加 show_offsets;3.14 增加 show_positions;next(未定版)增加 show_jit。
五、分析函数:单步直达输出的便捷入口
下列函数把输入直接转换为所需输出,适合只做单次操作、无需中间分析对象的场景。
5.1 code_info(x) 与 show_code(x, *, file=None)
code_info(x)返回一个多行格式化字符串,包含代码对象详细信息(Name、Filename、Argument count、Positional-only arguments、Kw-only arguments、Number of locals、Stack size、Flags、Constants、Names、Variable names、Free variables、Cell variables等);其输出内容高度依赖实现,可能随 VM 或版本变化(3.2 加入,3.7 起支持协程与异步生成器)。show_code(x, *, file=None)是print(code_info(x), file=file)的便捷简写,适合在解释器提示符下交互探索(3.4 起支持file参数)。
上述输出项与 Lib/dis.py 中 _format_code_info 逐行对应;Flags 的易读化由 pretty_flags 结合 COMPILER_FLAG_NAMES 完成(如 OPTIMIZED、NEWLOCALS、VARARGS、VARKEYWORDS、NESTED、GENERATOR、NOFREE、COROUTINE、ASYNC_GENERATOR、HAS_DOCSTRING、METHOD)。
5.2 dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False, show_jit=False)
反汇编对象 x,可接受:模块、类、方法、函数、生成器、异步生成器、协程、代码对象、源码字符串、原始字节码字节序列。
- 模块:反汇编其全部函数;
- 类:反汇编所有方法(含类方法、静态方法);
- 代码对象或原始字节码序列:每行一条指令;并递归反汇编嵌套代码对象(生成器表达式、嵌套函数、嵌套类体、注解作用域
annotation scopes的代码对象); - 字符串:先用内建
compile()编译成代码对象再反汇编; - 不传对象(
x=None):反汇编最近的 traceback(内部转调distb); depth:限制递归深度,None表示不限;depth=0表示完全不递归;- 输出写入
file(默认sys.stdout)。
从源码看,dis() 的分派逻辑非常直白(Lib/dis.py):先剥离方法取 __func__,再依次尝试 __code__(函数)、gi_code(生成器)、ag_code(异步生成器)、cr_code(协程);随后按“有 __dict__(类/模块)→ 有 co_code(代码对象)→ bytes/bytearray(原始字节码)→ str(源码)”的优先级分别处理。3.7 起支持递归反汇编与 depth 参数及协程对象,3.11 起支持 show_caches/adaptive,3.13/3.14 分别加入 show_offsets/show_positions。
5.3 distb(tb=None, *, file=None, show_caches=False, ...)
反汇编 traceback 栈顶函数,未传 tb 时使用最近 traceback;引发异常的指令会被标记。实现会先定位最内层 frame,再用 tb_lasti 作为“上一条已执行指令”传给 disassemble(Lib/dis.py)。典型场景:
>>> try:
... 1 / 0
... except ZeroDivisionError:
... dis.distb() # 标记出触发 ZeroDivisionError 的 BINARY_OP 指令
5.4 disassemble(code, lasti=-1, *, file=None, ...) 与别名 disco
反汇编代码对象,若给出 lasti 则标示最后一条指令。输出按列划分:
- 指令的源码位置(
show_positions为真时显示完整位置信息;默认仅显示行号); - 当前指令标记
-->; - 作为跳转目标的已标记指令
>>; - 指令地址;
- 操作码名称;
- 操作参数;
- 括号中的参数解释。
参数解释能识别:局部/全局变量名、常量值、分支目标、比较运算符。
5.5 get_instructions(x, *, first_line=None, show_caches=False, adaptive=False, show_jit=False)
返回函数/方法/源码字符串/代码对象上的指令迭代器,逐个产出 Instruction 具名元组。first_line 语义与 Bytecode 一致。3.11 起支持 show_caches 与 adaptive;3.13 起 show_caches 被弃用且不再产生效果——迭代器始终填充每条指令的 cache_info 字段,也不再为缓存条目单独生成 Instruction。Bytecode.__iter__ 正是基于它实现(Lib/dis.py)。
5.6 findlinestarts(code) 与 findlabels(code)
findlinestarts(code):生成器函数,基于代码对象的co_lines()方法找出“源码行的起始指令偏移”,产出(offset, lineno)对。3.10 起改用 PEP 626 的co_lines()替代旧的co_firstlineno/co_lnotab;3.6 起行号可能递减;3.13 起行号可为None(表示该字节码不映射到任何源码行)。findlabels(code):检测原始编译字节码中所有跳转目标偏移,返回偏移列表。
5.7 stack_effect(opcode, oparg=None, *, jump=None)
计算某 opcode(带参数 oparg)的栈效应(栈深度的净变化):
jump=True:返回“跳转发生”时的栈效应;jump=False:返回“跳转不发生”时的栈效应;jump=None(默认):返回两种情况下最大的栈效应。
3.13 起行为放宽:省略 oparg(或为 None)时按 oparg=0 计算(此前对使用参数的 opcode 会报错);对不使用参数的 opcode 传入整数 oparg 也不再报错,而是被忽略。该函数实际由 Lib/opcode.py 从 C 加速模块 _opcode 导入(from _opcode import stack_effect),见 Lib/opcode.py。
六、Instruction 与 Positions:指令的数据结构
get_instructions 与 Bytecode 将每条字节码操作以 Instruction 实例给出(Lib/dis.py 定义,继承自底层具名元组)。字段如下:
| 字段 | 含义 |
|---|---|
opcode |
操作数值码,对应下文指令编号与 opcode_collections |
opname |
操作的人类可读名称 |
baseopcode |
若该操作是特化指令,则为基础操作的数值码;否则等于 opcode |
baseopname |
基础操作的可读名称;否则等于 opname |
arg / oparg |
操作的数值参数(若有),否则为 None;oparg 是 arg 的别名(property,见 Lib/dis.py) |
argval |
解析后的参数值(若有),否则为 None |
argrepr |
参数的人类可读描述(若有),否则为空字符串 |
offset |
操作在字节码序列中的起始索引 |
start_offset |
含前导 EXTENDED_ARG 操作时的起始索引;否则等于 offset |
cache_offset |
紧随该操作的缓存条目的起始索引 |
end_offset |
紧随该操作的缓存条目的结束索引 |
starts_line |
该 opcode 是否开启一条新的源码行 |
line_number |
与该 opcode 关联的源码行号(若有),否则为 None |
is_jump_target |
是否有其他代码跳转到此处 |
jump_target |
若为跳转操作,则为跳转目标的字节码索引;否则为 None |
positions |
dis.Positions 对象,保存该指令覆盖的起止源码位置 |
cache_info |
该指令缓存条目的信息,为 (name, size, data) 三元组列表(name/size 描述缓存格式,data 为缓存内容);无缓存时为 None |
版本轨迹:字段 positions 3.11 加入;3.13 修改 starts_line,并新增 start_offset、cache_offset、end_offset、baseopname、baseopcode、jump_target、oparg、line_number、cache_info。
Positions(Lib/dis.py 中以 namedtuple 定义,字段默认 None)包含 lineno、end_lineno、col_offset、end_col_offset 四项,用于描述源码位置跨度;信息不可用时某些字段为 None。
>>> for instr in dis.get_instructions(myfunc):
... print(instr.offset, instr.opname, instr.arg, instr.argval)
0 RESUME 0 None
...
七、字节码指令语义总览
文档约定:下面用 STACK 指代解释器栈,并把它当作 Python 列表来叙述——栈顶即 STACK[-1]。指令分六类展开,且为便于检索,本文将文档中庞大的“杂项指令”按其职责进一步细分,但指令与语义全部忠实继承自官方文档。版本信息标注于指令名后。
7.1 通用指令
| 指令 | 语义 |
|---|---|
NOP |
什么都不做;被字节码优化器用作占位符,也用于生成行追踪事件 |
NOT_TAKEN(3.14) |
什么都不做;供解释器为 sys.monitoring 记录 BRANCH_LEFT/BRANCH_RIGHT 事件 |
POP_ITER(3.14) |
从栈顶移除迭代器 |
POP_TOP |
移除栈顶项(STACK.pop()) |
END_FOR(3.12) |
移除栈顶项,等价于 POP_TOP;用于循环结束时的清理 |
END_SEND(3.12) |
实现 del STACK[-2];用于生成器退出时的清理 |
COPY (i)(3.11) |
把第 i 项复制压栈且不删除原位置:assert i > 0; STACK.append(STACK[-i]) |
SWAP (i)(3.11) |
交换栈顶与第 i 项:STACK[-i], STACK[-1] = STACK[-1], STACK[-i] |
CACHE(3.11) |
并非真实指令,而是标记解释器把有用数据直接缓存在字节码中占用的额外空间。默认被所有 dis 工具隐藏,show_caches=True 可见;逻辑上属于前一条指令的一部分——许多 opcode 要求后跟精确数量的缓存并在运行时跳过它们。已填充的缓存看起来像任意指令,读写含 quickened 数据的原始自适应字节码时须格外小心 |
7.2 一元操作
一元操作取栈顶、施加操作、把结果压回栈。
| 指令 | 语义 |
|---|---|
UNARY_NEGATIVE |
STACK[-1] = -STACK[-1] |
UNARY_NOT |
STACK[-1] = not STACK[-1](3.13 起要求操作数为精确 bool) |
UNARY_INVERT |
STACK[-1] = ~STACK[-1] |
GET_ITER |
STACK[-1] = iter(STACK[-1]) |
GET_YIELD_FROM_ITER(3.5) |
若栈顶是生成器迭代器或协程对象则原样保留;否则 STACK[-1] = iter(STACK[-1]) |
TO_BOOL(3.13) |
STACK[-1] = bool(STACK[-1]) |
7.3 二元与就地操作
二元操作从栈上弹出顶部两项(STACK[-1] 与 STACK[-2]),执行后压回结果;就地操作在 STACK[-2] 支持时原地执行,结果 STACK[-1] 可以是(但不一定非是)原 STACK[-2]。
| 指令 | 语义 |
|---|---|
BINARY_OP (op)(3.11) |
依 op 实现二元/就地运算符:rhs = STACK.pop(); lhs = STACK.pop(); STACK.append(lhs op rhs)(3.14 起以 NB_SUBSCR 操作数支持下标取值的二元下标,取代旧的 BINARY_SUBSCR) |
STORE_SUBSCR |
key/container/value 依次出栈后执行 container[key] = value |
DELETE_SUBSCR |
del container[key] |
BINARY_SLICE(3.12) |
STACK.append(container[start:end]) |
STORE_SLICE(3.12) |
container[start:end] = value |
7.4 协程指令
| 指令 | 语义 |
|---|---|
GET_AWAITABLE (where)(3.5) |
STACK[-1] = get_awaitable(STACK[-1]):若为协程对象或带 CO_ITERABLE_COROUTINE 标志的生成器对象则原样返回,否则解析 o.__await__。where 非零时表示出现位置:1 = 调用 __aenter__ 之后;2 = 调用 __aexit__ 之后(3.11 起带 oparg) |
GET_AITER(3.5) |
STACK[-1] = STACK[-1].__aiter__()(3.7 起不再支持 __aiter__ 返回 awaitable) |
GET_ANEXT(3.5) |
STACK.append(get_awaitable(STACK[-1].__anext__())),get_awaitable 语义同 GET_AWAITABLE |
END_ASYNC_FOR(3.8) |
终止 async for 循环,处理等待下一项时抛出的异常;STACK[-2] 为异步可迭代对象、STACK[-1] 为异常,两者均弹出,非 StopAsyncIteration 则重抛(3.11 起栈上异常以单对象表示) |
CLEANUP_THROW(3.12) |
处理 generator.throw()/close() 穿过当前帧引发的异常:若栈顶是 StopIteration,弹出三个值并把其 value 成员压栈;否则重抛 STACK[-1] |
7.5 杂项指令(按职责细分)
文档将下列指令统一归入“Miscellaneous opcodes”。为便于学习,此处按功能细分为若干小组,指令与语义未做任何删减。
推导式与容器构建
| 指令 | 语义 |
|---|---|
SET_ADD (i)(用于集合推导式) |
item = STACK.pop(); set.add(STACK[-i], item) |
LIST_APPEND (i)(用于列表推导式) |
item = STACK.pop(); list.append(STACK[-i], item) |
MAP_ADD (i)(用于字典推导式,3.1;3.8 起值为 STACK[-1]、键为 STACK[-2]) |
value = STACK.pop(); key = STACK.pop(); dict.__setitem__(STACK[-i], key, value) |
BUILD_TUPLE (count) |
取 count 项建元组压栈(count == 0 时压入 ()) |
BUILD_LIST (count) |
同 BUILD_TUPLE,建列表 |
BUILD_SET (count) |
同 BUILD_TUPLE,建集合 |
BUILD_MAP (count) |
弹出 2 * count 项构建含 count 条目的字典 {..., STACK[-4]: STACK[-3], STACK[-2]: STACK[-1]}(3.5 起改为从栈项构建) |
BUILD_STRING (count)(3.6) |
连接栈上 count 个字符串并压栈 |
LIST_EXTEND (i)(3.9) |
seq = STACK.pop(); list.extend(STACK[-i], seq) |
SET_UPDATE (i)(3.9) |
seq = STACK.pop(); set.update(STACK[-i], seq) |
DICT_UPDATE (i)(3.9) |
map = STACK.pop(); dict.update(STACK[-i], map) |
DICT_MERGE (i)(3.9) |
类似 DICT_UPDATE,但对重复键抛异常 |
BUILD_SLICE (argc) |
argc 必须为 2 或 3,分别构造 slice(start, end) 或 slice(start, end, step) |
关于 SET_ADD/LIST_APPEND/MAP_ADD,虽然被加入的值或键值对被弹出,容器对象始终留在栈上,供循环的后续迭代继续使用。
返回值与生成器
| 指令 | 语义 |
|---|---|
RETURN_VALUE |
把 STACK[-1] 返回给函数调用者 |
YIELD_VALUE |
从生成器产出 STACK.pop()(3.11 起 oparg 为栈深;3.12 起 oparg 为异常块深度以便高效关闭生成器;3.13 起 oparg 为 1 表示属于 yield-from 或 await,否则为 0) |
RETURN_GENERATOR(3.11) |
从当前帧创建生成器/协程/异步生成器,作为代码对象首条指令;清空当前帧并返回新建的生成器 |
名称加载与存储、属性与作用域
| 指令 | 语义 |
|---|---|
STORE_NAME (namei) |
name = STACK.pop();namei 是名字在 co_names 中的下标;编译器尽量改用 STORE_FAST/STORE_GLOBAL |
STORE_GLOBAL (namei) |
同 STORE_NAME,但按全局名存储 |
STORE_ATTR (namei) |
obj = STACK.pop(); value = STACK.pop(); obj.name = value |
LOAD_CONST (consti) |
把 co_consts[consti] 压栈 |
LOAD_SMALL_INT (i)(3.14) |
把整数 i 压栈,i 必须在 range(256) 内 |
LOAD_NAME (namei) |
压入 co_names[namei] 关联的值;依次查 locals → globals → builtins |
LOAD_GLOBAL (namei) |
压入 co_names[namei>>1](3.11 起若 namei 最低位为 1,则先压入一个 NULL 再压全局变量) |
LOAD_ATTR (namei) |
若 namei 最低位为 0:以 getattr(STACK[-1], co_names[namei>>1]) 替换栈顶;若为 1:尝试装载名为 co_names[namei>>1] 的方法,弹出 STACK[-1]——有该方法则压入未绑定方法与原对象(self 留给 CALL/CALL_KW),否则压入 NULL 与属性查找结果(3.12 起 NULL/self 在属性或未绑定方法之前压栈) |
LOAD_SUPER_ATTR (namei)(3.12) |
实现 super() 零参与两参形式;弹出(栈顶往下)self、cls、全局 super 三项。namei 左移 2 位,最低位同 LOAD_ATTR 表示尝试方法装载;次低位为 1 表示两参 super |
LOAD_LOCALS(3.12) |
压入 locals 字典引用,为 LOAD_FROM_DICT_OR_DEREF/LOAD_FROM_DICT_OR_GLOBALS 准备命名空间字典 |
LOAD_FROM_DICT_OR_GLOBALS (i)(3.12) |
弹出映射并在其中查找 co_names[namei];未找到则按 LOAD_GLOBAL 方式查 globals 与 builtins;用于类体内注解作用域中加载全局变量 |
LOAD_FAST (var_num) |
压入 co_varnames[var_num] 局部变量引用(3.12 起仅用于保证已初始化的场景,不会抛 UnboundLocalError) |
LOAD_FAST_BORROW (var_num)(3.14) |
以借用引用压入局部变量 |
LOAD_FAST_LOAD_FAST (var_nums)(3.13) |
压入 co_varnames[var_nums >> 4] 与 co_varnames[var_nums & 15] 两个引用(一次装载两个局部变量的超级指令) |
LOAD_FAST_BORROW_LOAD_FAST_BORROW(3.14) |
同上,但两次均为借用引用 |
LOAD_FAST_CHECK (var_num)(3.12) |
压入局部变量引用,未初始化则抛 UnboundLocalError |
LOAD_FAST_AND_CLEAR (var_num)(3.12) |
压入局部变量引用(未初始化则压 NULL)并把该局部槽位置为 NULL |
STORE_FAST (var_num) |
把 STACK.pop() 存入 co_varnames[var_num] |
STORE_FAST_STORE_FAST (var_nums)(3.13) |
STACK[-1] 存入 var_nums >> 4,STACK[-2] 存入 var_nums & 15 |
STORE_FAST_LOAD_FAST (var_nums)(3.13) |
弹出并存入一个局部变量,同时压入另一个局部变量引用 |
DELETE_FAST (var_num) |
删除 co_varnames[var_num] 局部变量 |
MAKE_CELL (i)(3.11) |
在槽 i 创建新 cell;若槽非空则把值存入新 cell |
LOAD_DEREF (i) |
从 fast locals 的 cell 槽 i 装载并把对象引用压栈(3.11 起 i 不再按 co_varnames 长度偏移) |
LOAD_FROM_DICT_OR_DEREF (i)(3.12) |
弹出映射查找槽 i 对应名字,未找到再从 cell 装载(类似 LOAD_DEREF);用于类体内闭包变量与注解作用域(取代旧的 LOAD_CLASSDEREF) |
STORE_DEREF (i) |
把 STACK.pop() 存入 fast locals 槽 i 的 cell(3.11 起偏移规则同上) |
DELETE_DEREF (i)(3.2) |
清空 fast locals 槽 i 的 cell,供 del 语句使用(3.11 起偏移规则同上) |
COPY_FREE_VARS (n)(3.11) |
把 n 个自由(闭包)变量从 closure 复制进帧,免去调用方特殊处理 |
LOAD_BUILD_CLASS |
把 builtins.__build_class__ 压栈,随后调用以构造类 |
UNPACK_SEQUENCE (count) |
把 STACK[-1] 解包为恰好 count 个值,从右到左压栈:assert len(STACK[-1]) == count |
UNPACK_EX (counts) |
实现带星号目标(如 a, *b, c = d)的赋值:总数可少于可迭代项数,其中一个新值是剩余项的列表。列表前后值的数量都限制在 255;操作数低字节 = 列表前的数量,高字节(经 EXTENDED_ARG) = 列表后的数量 |
IMPORT_NAME (namei) |
导入模块 co_names[namei],弹出栈顶两项作为 __import__ 的 fromlist 与 level 参数,压入模块对象;命名空间本身不受影响,随后的 STORE_FAST 才修改它 |
IMPORT_FROM (namei) |
从 STACK[-1] 模块装载属性 co_names[namei],压栈以待 STORE_FAST 存储 |
比较、成员判断与模式匹配
| 指令 | 语义 |
|---|---|
COMPARE_OP (opname) |
执行布尔运算;操作名见 cmp_op[opname >> 5];若 opname 第五低 bit(opname & 16)置位则结果强转为 bool(3.13 起) |
IS_OP (invert)(3.9) |
is 比较;invert 为 1 时是 is not |
CONTAINS_OP (invert)(3.9) |
in 比较;invert 为 1 时是 not in |
GET_LEN(3.10) |
STACK.append(len(STACK[-1])),用于 match 语句结构比对 |
MATCH_MAPPING(3.10) |
若 STACK[-1] 是 collections.abc.Mapping 实例(技术上:其 tp_flags 含 Py_TPFLAGS_MAPPING)压 True,否则 False |
MATCH_SEQUENCE(3.10) |
若栈顶是 Sequence 实例且不是 str/bytes/bytearray(技术上:tp_flags 含 Py_TPFLAGS_SEQUENCE)压 True,否则 False |
MATCH_KEYS(3.10) |
STACK[-1] 为键元组、STACK[-2] 为匹配主体;若主体含全部键则压入对应值元组,否则压 None(3.11 起不再额外压布尔) |
MATCH_CLASS (count)(3.10) |
STACK[-1] 为关键字属性名元组、STACK[-2] 为被匹配的类、STACK[-3] 为主体;count 为位置子模式数。三者出栈:若主体是该类实例且具备所需属性,压入抽取的属性元组,否则压 None(3.11 起不再额外压布尔) |
跳转与控制流
| 指令 | 语义 |
|---|---|
JUMP_FORWARD (delta) |
字节码计数器加 delta |
JUMP_BACKWARD (delta)(3.11) |
计数器减 delta,检查中断 |
JUMP_BACKWARD_NO_INTERRUPT (delta)(3.11) |
计数器减 delta,不检查中断 |
POP_JUMP_IF_TRUE (delta) |
栈顶为真则加 delta,栈顶弹出(3.11 起参数为相对量并曾作为伪指令,3.12 起不再是伪指令;3.13 起要求精确 bool 操作数) |
POP_JUMP_IF_FALSE (delta) |
栈顶为假则加 delta,栈顶弹出(版本轨迹同前) |
POP_JUMP_IF_NOT_NONE (delta)(3.11) |
栈顶非 None 则加 delta,栈顶弹出(3.12 起非伪指令) |
POP_JUMP_IF_NONE (delta)(3.11) |
栈顶为 None 则加 delta,栈顶弹出(3.12 起非伪指令) |
FOR_ITER (delta) |
栈顶为迭代器;调用其 __next__:得到新值则压栈(迭代器保留在下方);迭代器耗尽则计数器加 delta(3.12 起耗尽时不再弹出迭代器) |
SEND (delta)(3.11) |
STACK[-1] = STACK[-2].send(STACK[-1]),用于 yield from 与 await;若调用抛 StopIteration,弹出栈顶、压入异常 value、计数器加 delta |
调用、函数构造与异常抛出
| 指令 | 语义 |
|---|---|
CALL (argc)(3.11) |
以 argc 个参数调用栈顶 callable;栈上自下而上为:callable、self 或 NULL、其余位置参数;argc 不含 self。弹出全部实参与 callable 后压入返回值(3.13 起 callable 恒在栈上同一位置;3.13 起含关键字参数的调用改由 CALL_KW 处理) |
CALL_KW (argc)(3.13) |
与 CALL 类似,但含一个或多个命名参数;栈上在实参之上还有命名实参以及关键字名元组;argc 为位置+命名参数总数(不含 self) |
CALL_FUNCTION_EX (flags)(3.6) |
以可变位置/关键字参数调用;flags 最低位为 1 表示栈顶是含额外关键字参数的映射;调用前映射与可迭代对象分别被解包为关键字/位置参数 |
PUSH_NULL(3.11) |
压入一个 NULL,用于在调用序列中与 LOAD_ATTR 对非方法调用压入的 NULL 对齐 |
MAKE_FUNCTION |
用 STACK[-1] 的代码对象构建新函数对象压栈(3.10 起 0x04 标志从字典变为字符串元组;3.11 起移除限定名;3.13 起额外函数属性改为 SET_FUNCTION_ATTRIBUTE 设置) |
SET_FUNCTION_ATTRIBUTE (flag)(3.13) |
函数在 STACK[-1]、属性值在 STACK[-2],两者出栈后函数留在栈顶;flag:0x01 位置参数默认值元组;0x02 仅关键字参数默认值字典;0x04 参数注解字符串元组;0x08 自由变量 cell 元组(构成闭包);0x10 函数的 annotate function(3.14 加入) |
RAISE_VARARGS (argc) |
依 argc 实现 raise 三形式:0 = 重抛;1 = raise STACK[-1];2 = raise STACK[-2] from STACK[-1](以 __cause__ 链接) |
RESUME (context)(3.11) |
无操作但执行内部追踪、调试与优化检查。context 最低两 bit 表示出现位置:0 函数起点(非生成器/协程/异步生成器)、1 在 yield 表达式后、2 在 yield from 后、3 在 await 后;下一 bit 为 1 表示处于 except 深度 1(3.13 起编码扩展) |
CALL_INTRINSIC_1(3.12) |
以单参数调用内建内置函数,STACK[-1] 即参数也即结果位置。操作数决定具体函数(详见下表) |
CALL_INTRINSIC_2(3.12) |
以两个参数调用内置函数:arg2 = STACK.pop(); arg1 = STACK.pop(); STACK.append(result) |
LOAD_SPECIAL(3.14) |
对 STACK[-1] 做特殊方法查找:type(STACK[-1]).__xxx__ 是方法则留 方法; 对象;否则留 对象.__xxx__; NULL |
CONVERT_VALUE (oparg)(3.13) |
依 oparg 转字符串:1 = str、2 = repr、3 = ascii;用于 f-string 实现 |
FORMAT_SIMPLE(3.13) |
调用 value.__format__("") 格式化栈顶值;用于 f-string |
FORMAT_WITH_SPEC(3.13) |
用给定 spec 格式化:result = value.__format__(spec);用于 f-string |
BUILD_TEMPLATE(3.14) |
从字符串元组与插值元组构造 string.templatelib.Template:_build_template(strings, interpolations) |
BUILD_INTERPOLATION (format)(3.14) |
从值及其源表达式构造 Interpolation。format:无转换且无格式规格时为 2;最低 bit 置位表示含格式规格;format >> 2 非零表示含转换(0 无、1=!s、2=!r、3=!a) |
LOAD_COMMON_CONSTANT(3.14) |
压入解释器内置硬编码列表中的常见常量;assert 语句用它加载 AssertionError |
CALL_INTRINSIC_1 的操作数取值(文档表格原样继承):
| 操作数 | 说明 |
|---|---|
INTRINSIC_1_INVALID |
无效 |
INTRINSIC_PRINT |
打印参数到 stdout,REPL 中使用 |
INTRINSIC_IMPORT_STAR |
对指定模块执行 import * |
INTRINSIC_STOPITERATION_ERROR |
从 StopIteration 中提取返回值 |
INTRINSIC_ASYNC_GEN_WRAP |
包装异步生成器值 |
INTRINSIC_UNARY_POSITIVE |
执行一元 + |
INTRINSIC_LIST_TO_TUPLE |
列表转元组 |
INTRINSIC_TYPEVAR |
创建 typing.TypeVar |
INTRINSIC_PARAMSPEC |
创建 typing.ParamSpec |
INTRINSIC_TYPEVARTUPLE |
创建 typing.TypeVarTuple |
INTRINSIC_SUBSCRIPT_GENERIC |
返回以参数下标的 typing.Generic |
INTRINSIC_TYPEALIAS |
创建 typing.TypeAliasType,用于 type 语句;参数为名字、类型参数与值的元组 |
CALL_INTRINSIC_2 的操作数取值:
| 操作数 | 说明 |
|---|---|
INTRINSIC_2_INVALID |
无效 |
INTRINSIC_PREP_RERAISE_STAR |
从 try-except* 计算要抛出的 ExceptionGroup |
INTRINSIC_TYPEVAR_WITH_BOUND |
创建带 bound 的 TypeVar |
INTRINSIC_TYPEVAR_WITH_CONSTRAINTS |
创建带约束的 TypeVar |
INTRINSIC_SET_FUNCTION_TYPE_PARAMS |
设置函数 __type_params__ 属性 |
INTRINSIC_ADD_CONDITIONAL_ANNOTATION |
向 __conditional_annotations__ 集合添加注解索引 |
异常处理辅助
| 指令 | 语义 |
|---|---|
POP_EXCEPT |
弹出用于恢复异常状态的值(3.11 起栈上异常为单对象表示) |
RERAISE(3.9) |
重抛栈顶异常;oparg 非零则再弹一个值用于设置当前帧 f_lasti(3.11 起单对象表示) |
PUSH_EXC_INFO(3.11) |
弹出栈顶值,把当前异常压栈,再把原弹出的值压回;用于异常处理器 |
CHECK_EXC_MATCH(3.11) |
为 except 做异常匹配:测试 STACK[-2] 是否为匹配 STACK[-1] 的异常;弹出 STACK[-1] 并压入布尔结果 |
CHECK_EG_MATCH(3.11) |
为 except* 做匹配:对代表 STACK[-2] 的异常组执行 split(STACK[-1]);匹配时弹出两项、压入不匹配子组(完全匹配则为 None)与匹配子组;不匹配时弹一项压 None |
WITH_EXCEPT_START(3.9) |
调用栈上第 4 位的函数,传入表示栈顶异常的 (type, val, tb);用于 with 中发生异常时执行 context_manager.__exit__(*exc_info())(3.11 起 __exit__ 位于栈第 4 位,异常单对象表示) |
SETUP_ANNOTATIONS(3.6) |
检查 locals() 是否定义了 __annotations__,未定义则初始化为空 dict;仅当类体或模块体含静态变量注解时才发射 |
7.6 辅助与边界指令
| 指令 | 语义 |
|---|---|
EXTENDED_ARG (ext) |
作为任意“参数超过默认 1 字节”的 opcode 的前缀;ext 提供参数的高位字节。每个 opcode 最多允许三个 EXTENDED_ARG 前缀,组合出 2~4 字节的参数 |
HAVE_ARGUMENT |
并非真正的 opcode:它是 [0,255] 区间内“不使用参数”与“使用参数”(< HAVE_ARGUMENT 与 >= HAVE_ARGUMENT)两类 opcode 的分界线。3.6 起每条指令都有参数但 HAVE_ARGUMENT 以下的忽略之;3.12 起模块中加入伪指令后,不能再用它与 HAVE_ARGUMENT 比较来判断是否使用参数;3.13 起已弃用,改用 hasarg 集合 |
八、伪指令(Pseudo-instructions)
以下 opcode 不会出现在最终 Python 字节码中:它们只供编译器使用,在字节码生成前被真实 opcode 替换或删除。
| 伪指令 | 语义与去向 |
|---|---|
SETUP_FINALLY (target) |
为后续代码块设置异常处理器;异常发生时把值栈恢复到当前状态并跳转到 target 处理器 |
SETUP_CLEANUP (target) |
同 SETUP_FINALLY,但异常发生时额外把 lasti 压栈供 RERAISE 恢复 |
SETUP_WITH (target) |
同 SETUP_CLEANUP,但异常时多弹一项再转交处理器;用于 with/async with(其会向栈上压入 __enter__/__aenter__ 的返回值) |
POP_BLOCK |
标记与最近一个 SETUP_FINALLY/SETUP_CLEANUP/SETUP_WITH 关联的代码块结束 |
JUMP / JUMP_NO_INTERRUPT |
无方向相对跳转,由汇编器替换为有向(前向/后向)版本 |
JUMP_IF_TRUE / JUMP_IF_FALSE |
不改变栈的条件跳转,被序列 COPY 1、TO_BOOL、POP_JUMP_IF_TRUE/FALSE 取代 |
LOAD_CLOSURE (i) |
压入 fast locals 槽 i 中 cell 的引用;汇编器以 LOAD_FAST 替换(3.13 起为伪指令) |
九、Opcode 集合:自动内省字节码
这些集合用于对字节码指令做自动内省(可在 Lib/opcode.py 看到它们由 C 层的 _opcode.has_* 谓词生成)。3.12 起,集合同样包含伪指令与 instrumented 指令(即 opcode 值 >= MIN_PSEUDO_OPCODE 与 >= MIN_INSTRUMENTED_OPCODE 的部分)。
| 集合 | 内容 |
|---|---|
opname |
操作名序列,可用字节码值索引 |
opmap |
操作名 → 字节码编号的字典(3.12 起由 _opcode_metadata 提供,Lib/opcode.py) |
cmp_op |
所有比较操作名序列(源码中为 ('<', '<=', '==', '!=', '>', '>=')) |
hasarg(3.12) |
使用其参数的字节码序列 |
hasconst |
访问常量的字节码序列 |
hasfree |
访问自由(闭包)变量的字节码序列。“free”指被内层作用域引用的当前作用域名、或当前作用域引用的外层作用域名,不包括全局与 builtins 引用 |
hasname |
按名访问属性的字节码序列 |
hasjump(3.13) |
带跳转目标的字节码序列,所有跳转均为相对跳转 |
haslocal |
访问局部变量的字节码序列 |
hascompare |
布尔运算的字节码序列(源码中即 [opmap["COMPARE_OP"]]) |
hasexc(3.12) |
设置异常处理器的字节码序列 |
hasjrel |
带相对跳转目标的字节码序列(3.13 起弃用——所有跳转都是相对的,改用 hasjump) |
hasjabs |
带绝对跳转目标的字节码序列(3.13 起弃用,恒为空) |
利用这些集合可实现通用字节码工具,例如:用 opmap['LOAD_GLOBAL'] 判断特化后的指令族;用 hasjump 枚举所有分支指令;结合 Lib/opcode.py 中的 _cache_format 了解 LOAD_GLOBAL、BINARY_OP 等指令内联缓存的布局(counter、index、版本号等字段)。
十、实战:用 dis 定位崩溃指令与探索嵌套代码
场景一:定位异常指令。 无论是 distb() 输出的 --> 标记、disassemble(code, lasti) 的 lasti 参数,还是 Bytecode.from_traceback(tb) 自动把 current_offset 指向异常指令的能力,都是逐行排查运行时错误的利器。
场景二:探索嵌套作用域。 dis() 与 disassemble() 会递归进入生成器表达式、嵌套函数、嵌套类体与注解作用域的代码对象(Lib/dis.py 的 _disassemble_recursive 实现了该递归)。例如对含推导式的函数调用 dis.dis(),输出中会出现 Disassembly of <listcomp>、<setcomp>、<dictcomp>、<genexpr> 等嵌套节,帮助你理解推导式如何被编译为独立代码对象。
场景三:观察自适应特化。 用 adaptive=True(或 CLI 的 -S)与 show_caches=True(CLI 的 -C)组合,可以看到常规版本下不存在的特化版本——例如 LOAD_GLOBAL 在运行时“变热”后会 quicken 为带内联缓存(counter/index/版本字段)的特化版本,甚至产生包含 CACHE 条目的形态;dis 在 adaptive 模式下会通过 C 扩展的 get_executor 展开 ENTER_EXECUTOR 所指向的执行器(见 Lib/dis.py 的 _get_code_array,当遇到 ENTER_EXECUTOR 且未设 show_jit 时,它会尝试提取对应的执行器字节码替换显示)。
场景四:把 dis 作为测试基线。 仓库自带的 Lib/test/test_dis.py 是权威的“预期输出”样例,它覆盖了 dis/distb/Bytecode/Instruction 字段及各类指令的反汇编结果。若你编写了依赖字节码的工具,可参照该测试文件的断言方式,用 Instruction 具名元组的 opname/argval/positions 字段做结构化校验,而不是脆弱的文本比对。
十一、结语
dis 模块是一座连接“Python 源码”与“CPython 虚拟机”的桥梁。借助它,你能观察到编译器为每个语法结构选择的指令形态(如 BINARY_OP vs BINARY_SLICE、LOAD_FAST_BORROW vs LOAD_FAST_CHECK),也能窥见 3.11+ 解释器“自适应字节码 + 内联缓存 + 执行器”的运行时优化如何在原始字节码之上叠加特化层。在使用时请始终记得官方那句 impl-detail 警告——字节码属于解释器实现细节,跨版本、跨 VM 均无稳定承诺;而 dis 的输出格式本身(逻辑标签、CACHE 隐藏与否、是否显示偏移/位置)也在随版本持续演进。最权威的指令清单,始终是与你所运行的二进制完全匹配的那份 Lib/opcode.py 与 Include/opcode.h,以及当前 Doc/library/dis.rst 中列出的全部语义描述。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K635- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python70
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java161
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java90
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript120
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300