首页
/ CPython 字节码反汇编完全指南:深入解析 `dis` 模块、命令行工具与字节码指令集

CPython 字节码反汇编完全指南:深入解析 `dis` 模块、命令行工具与字节码指令集

2026-09-07 11:37:54作者:卓炯娓

本指南以 CPython 仓库官方文档 Doc/library/dis.rst 为骨架,结合 Lib/dis.pyLib/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:新增命令行 -Pshow_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+ 行为之上):

  • 行首的数字 23源码行号
  • 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.pymain()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_positionsnext(未定版)增加 show_jit

五、分析函数:单步直达输出的便捷入口

下列函数把输入直接转换为所需输出,适合只做单次操作、无需中间分析对象的场景。

5.1 code_info(x)show_code(x, *, file=None)

  • code_info(x) 返回一个多行格式化字符串,包含代码对象详细信息(NameFilenameArgument countPositional-only argumentsKw-only argumentsNumber of localsStack sizeFlagsConstantsNamesVariable namesFree variablesCell 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 完成(如 OPTIMIZEDNEWLOCALSVARARGSVARKEYWORDSNESTEDGENERATORNOFREECOROUTINEASYNC_GENERATORHAS_DOCSTRINGMETHOD)。

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 作为“上一条已执行指令”传给 disassembleLib/dis.py)。典型场景:

>>> try:
...     1 / 0
... except ZeroDivisionError:
...     dis.distb()          # 标记出触发 ZeroDivisionError 的 BINARY_OP 指令

5.4 disassemble(code, lasti=-1, *, file=None, ...) 与别名 disco

反汇编代码对象,若给出 lasti 则标示最后一条指令。输出按列划分:

  1. 指令的源码位置(show_positions 为真时显示完整位置信息;默认仅显示行号);
  2. 当前指令标记 -->
  3. 作为跳转目标的已标记指令 >>
  4. 指令地址;
  5. 操作码名称;
  6. 操作参数;
  7. 括号中的参数解释。

参数解释能识别:局部/全局变量名、常量值、分支目标、比较运算符。

5.5 get_instructions(x, *, first_line=None, show_caches=False, adaptive=False, show_jit=False)

返回函数/方法/源码字符串/代码对象上的指令迭代器,逐个产出 Instruction 具名元组。first_line 语义与 Bytecode 一致。3.11 起支持 show_cachesadaptive3.13 起 show_caches 被弃用且不再产生效果——迭代器始终填充每条指令的 cache_info 字段,也不再为缓存条目单独生成 InstructionBytecode.__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

六、InstructionPositions:指令的数据结构

get_instructionsBytecode 将每条字节码操作以 Instruction 实例给出(Lib/dis.py 定义,继承自底层具名元组)。字段如下:

字段 含义
opcode 操作数值码,对应下文指令编号与 opcode_collections
opname 操作的人类可读名称
baseopcode 若该操作是特化指令,则为基础操作的数值码;否则等于 opcode
baseopname 基础操作的可读名称;否则等于 opname
arg / oparg 操作的数值参数(若有),否则为 Noneopargarg 的别名(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_offsetcache_offsetend_offsetbaseopnamebaseopcodejump_targetopargline_numbercache_info

PositionsLib/dis.py 中以 namedtuple 定义,字段默认 None)包含 linenoend_linenocol_offsetend_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() 零参与两参形式;弹出(栈顶往下)selfcls、全局 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 >> 4STACK[-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__fromlistlevel 参数,压入模块对象;命名空间本身不受影响,随后的 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_flagsPy_TPFLAGS_MAPPING)压 True,否则 False
MATCH_SEQUENCE(3.10) 若栈顶是 Sequence 实例且不是 str/bytes/bytearray(技术上:tp_flagsPy_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 fromawait;若调用抛 StopIteration,弹出栈顶、压入异常 value、计数器加 delta

调用、函数构造与异常抛出

指令 语义
CALL (argc)(3.11) argc 个参数调用栈顶 callable;栈上自下而上为:callable、selfNULL、其余位置参数;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 函数起点(非生成器/协程/异步生成器)、1yield 表达式后、2yield from 后、3await 后;下一 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 = str2 = repr3 = 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) 从值及其源表达式构造 Interpolationformat:无转换且无格式规格时为 2;最低 bit 置位表示含格式规格;format >> 2 非零表示含转换(0 无、1=!s2=!r3=!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 1TO_BOOLPOP_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_GLOBALBINARY_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 条目的形态;disadaptive 模式下会通过 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_SLICELOAD_FAST_BORROW vs LOAD_FAST_CHECK),也能窥见 3.11+ 解释器“自适应字节码 + 内联缓存 + 执行器”的运行时优化如何在原始字节码之上叠加特化层。在使用时请始终记得官方那句 impl-detail 警告——字节码属于解释器实现细节,跨版本、跨 VM 均无稳定承诺;而 dis 的输出格式本身(逻辑标签、CACHE 隐藏与否、是否显示偏移/位置)也在随版本持续演进。最权威的指令清单,始终是与你所运行的二进制完全匹配的那份 Lib/opcode.pyInclude/opcode.h,以及当前 Doc/library/dis.rst 中列出的全部语义描述。

热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
603
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23