大家好,我是正在实战各种 AI 项目的程序员晚枫。

理解常用字节码指令,看懂 dis 模块的输出。这一讲,你终于能看懂 Python 源代码背后的秘密了。


📖 开篇:字节码是什么?

我们写 Python 代码,Python 不会直接执行。

它会先「编译」成一种中间格式——字节码(Bytecode),然后由 Python 虚拟机(PVM)执行。

1
2
3
4
5
6
7
8
# 这行简单的代码
x = 1 + 2

# 会被编译成字节码,大致是:
# LOAD_CONST 1 # 把常量 1 压入栈
# LOAD_CONST 2 # 把常量 2 压入栈
# BINARY_ADD # 弹出两个数相加,压入结果
# STORE_NAME 0 # 把结果存入变量 x

Python 的 dis 模块可以让我们看到这些字节码:

1
2
import dis
dis.dis('x = 1 + 2')

小技巧:在 Jupyter Notebook 里,用 dis.dis(函数名) 可以分析任何函数的字节码。这是理解 Python 运行机制的第一步。


📖 加载/存储指令

这是最常用的指令,用来在变量和栈之间移动数据:

指令作用示例
LOAD_CONST把常量加载到栈上x = 1LOAD_CONST 1
LOAD_FAST加载局部变量print(x)LOAD_FAST x
LOAD_GLOBAL加载全局变量print(len)LOAD_GLOBAL len
STORE_FAST把栈顶的值存到局部变量x = 1STORE_FAST x
STORE_GLOBAL存到全局变量global x; x = 1
DELETE_FAST删除局部变量del xDELETE_FAST x

看一个完整例子:

1
2
3
4
5
6
7
8
9
import dis

def example():
x = 1 # STORE_FAST x
y = x + 1 # LOAD_FAST x → LOAD_CONST 1 → BINARY_ADD → STORE_FAST y
z = x * y # LOAD_FAST x → LOAD_FAST y → BINARY_MULTIPLY → STORE_FAST z
return z

dis.dis(example)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2           0 LOAD_CONST               1 (1)
2 STORE_FAST 0 (x)

3 4 LOAD_FAST 0 (x)
6 LOAD_CONST 2 (2)
8 BINARY_ADD
10 STORE_FAST 1 (y)

4 12 LOAD_FAST 0 (x)
14 LOAD_FAST 1 (y)
16 BINARY_MULTIPLY
18 STORE_FAST 2 (z)

5 20 LOAD_FAST 2 (z)
22 RETURN_VALUE

你注意到了吗?

  • x 存储在局部变量槽 0((x) 是注释)
  • 每一步都有「操作码 + 操作数」的组合
  • RETURN_VALUE 负责把最终结果返回

🔧 运算指令

栈式虚拟机的工作方式:先压入操作数,再执行运算指令

指令作用对应操作
BINARY_ADD弹出两个值相加a + b
BINARY_SUBTRACT弹出两个值相减a - b
BINARY_MULTIPLY弹出两个值相乘a * b
BINARY_TRUE_DIVIDE弹出两个值相除a / b
BINARY_FLOOR_DIVIDE整除a // b
BINARY_MODULO取模a % b
BINARY_POWER幂运算a ** b

比较运算

指令作用
COMPARE_OP比较运算(<、>、== 等)
POP_JUMP_IF_TRUE栈顶为真则跳转
POP_JUMP_IF_FALSE栈顶为假则跳转
1
2
3
4
5
6
7
def compare_demo(a, b):
if a > b:
return True
else:
return False

dis.dis(compare_demo)

📞 函数调用指令

函数调用是字节码里最复杂的操作之一:

指令作用
CALL_FUNCTION调用函数(Python 3.11 之前)
CALL_METHOD调用方法(Python 3.11 之前)
PREP_CALL准备调用(Python 3.11+)
RETURN_VALUE返回值
1
2
3
4
5
6
7
8
def greet(name):
return f"Hello, {name}!"

def main():
msg = greet("Python")
print(msg)

dis.dis(main)

Python 3.11 的重大变化

Python 3.11 对字节码做了大幅优化,指令更精简:

  • CALL_FUNCTION 被替换为 PRECALL + CALL
  • 增加了 SWAPCOPY 等新指令
  • 异常处理更快

🔍 实战:用 dis 分析性能问题

字节码分析不仅是学习工具,还能帮我们优化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import dis

# 方式 1:列表推导式
def list_comp():
return [x * 2 for x in range(1000)]

# 方式 2:普通循环
def for_loop():
result = []
for x in range(1000):
result.append(x * 2)
return result

print("=== 列表推导式 ===")
dis.dis(list_comp)
print()
print("=== 普通循环 ===")
dis.dis(for_loop)

看字节码行数:列表推导式往往比等效的 for 循环更少字节码指令,执行更快。


⚠️ 常见陷阱

陷阱1:局部变量命名冲突

1
2
3
4
5
6
7
8
9
10
# 慢写法
a = 1
def slow():
a = a + 1 # LOAD_GLOBAL a(全局查找,慢!)
return a

# 快写法
def fast():
b = a + 1 # LOAD_FAST b(局部查找,快!)
return b

陷阱2:在循环内重复获取属性

1
2
3
4
5
6
# 慢
for obj in objects:
print(obj.name) # 每次都要查找 name 属性

# 快
names = [obj.name for obj in objects] # 用列表推导式

💡 本节作业

  1. dis.dis() 分析你最常用的一个函数
  2. 找出函数里最耗时的字节码是什么
  3. 尝试用列表推导式替换一个普通 for 循环,用 dis 对比

🎯 本讲总结

加载/存储指令:LOAD_FAST、STORE_FAST、LOAD_CONST——栈式虚拟机的基本操作。

运算指令:BINARY_ADD、BINARY_MULTIPLY 等——先压栈,再运算。

函数调用:CALL_FUNCTION、RETURN_VALUE——复杂的调用约定。

实战工具dis 模块是分析字节码的瑞士军刀。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》


🔗 课程导航

上一讲:字节码执行 | 下一讲:数字类型实现


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!