解密Python字节码:第四列那个神秘数字到底代表什么TLDR:第 4 列数字没有固定含义。其是opcode 的操作数(oparg),含义完全由该 opcode 决定,可能是索引、计数、枚举或位编码。
Python不同版本字节码有变化,本文使用3.13环境。仍然用上一篇文章的5行示例代码:
x = 1def foo(): print(x) x += 1foo()
dis反编译字节码:
偏移量 源码行号 OPCODE oparg (dis反查出人类可读解释) 0 RESUME 0 1 LOAD_CONST 0 (1) STORE_NAME 0 (x) 2 LOAD_CONST 1 (<code object foo at 0x60..., file "e.py", line 2>) MAKE_FUNCTION STORE_NAME 1 (foo) 5 LOAD_NAME 1 (foo) PUSH_NULL CALL 0 POP_TOP RETURN_CONST 2 (None)foo 2 RESUME 0 3 LOAD_GLOBAL 1 (print + NULL) LOAD_FAST_CHECK 0 (x) CALL 1 POP_TOP 4 LOAD_FAST 0 (x) LOAD_CONST 1 (1) BINARY_OP 13 (+=) STORE_FAST 0 (x) RETURN_CONST 0 (None)
Python 3.13 的 dis 输出中,第 4 列的数字是该指令的 操作数(oparg)。 它不是“第几行”“第几条指令”,而是 紧跟在 opcode 后面的参数值,其语义 完全由具体指令决定。
这份输出里,其实每一列含义是固定的:
偏移量 源码行号 OPCODE oparg (人类可读解释)
一、列结构澄清(Python 3.13)
以这一行为例:
3 LOAD_GLOBAL 1 (print + NULL)
| |
|---|
| 源码行号(line number table 映射结果) |
| |
| |
| |
真正的“第 4 列数字”是 1,它是 oparg。
二、oparg 的本质是什么?
oparg 是“指令参数”,但它不是统一语义的数字。
oparg 的意义 = 由 opcode 决定
它通常是以下几类之一:
| |
|---|
| 指向 co_consts / co_names / co_varnames / co_cellvars |
| |
| |
| |
三、结合例子逐条解释
LOAD_CONST 0 (1)
LOAD_CONST 0 (1)
等价于:
push(co_consts[0]) // 1
STORE_NAME 0 (x)
STORE_NAME 0 (x)
LOAD_GLOBAL 1 (print + NULL)
LOAD_GLOBAL 1 (print + NULL)
这里是 Python 3.11+ 的重要变化点。
oparg = 1 表示什么?
- •
oparg >> 1 → co_names 索引 - •
oparg & 1 → 是否需要 NULL(用于 vectorcall)
也就是:
name_index = 1 >> 1 = 0push_null = 1 & 1 = 1
所以:
(print + NULL)
这是 调用路径优化的一部分。
LOAD_FAST_CHECK 0 (x)
LOAD_FAST_CHECK 0 (x)
- • CHECK = 如果未绑定,抛 UnboundLocalError
这是笔者上篇文章那个:
print(x)x += 1
会报错的根本原因之一。
CALL 1
CALL 1
BINARY_OP 13 (+=)
BINARY_OP 13 (+=)
NB_INPLACE_ADD
不是 magic number,是 CPython 内部定义的操作码映射。
四、为什么 Python 3.13 的 dis 看起来“数字变多了”?
原因是 CPython 3.11+ 引入了统一的 wordcode + specializing interpreter:
- • 很多指令被合并(如
BINARY_ADD → BINARY_OP)
