dis.dis() 默认不递归展开嵌套 code_object,故看不到循环或条件跳转目标;lambda 和 comprehension 的 code_object 无真实名称;内置函数无字节码,dis 仅显示 CALL_FUNCTION;对比字节码应关注栈操作与跳转频率而非指令数。

dis.dis() 为什么看不到循环或条件的跳转目标?
因为 dis.dis() 默认只反汇编顶层函数或代码对象,不递归展开嵌套的 code_object(比如 for 循环体、if 分支里的独立代码块)。你看到的 JUMP_FORWARD 或 POP_JUMP_IF_FALSE 后面的偏移量,实际指向的是同一代码对象内的字节码位置,但默认输出不标注标签或行号映射。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
dis.get_instructions()获取带offset、argval、starts_line的完整指令列表,再手动对照跳转参数 - 对含
if/for的函数,先用func.__code__.co_consts找出其中的嵌套code_object,再对每个调用dis.dis(const) - 加参数
dis.dis(func, depth=2)(Python 3.11+)可自动展开部分嵌套结构
lambda 和 comprehension 的字节码为何看起来“没名字”?
它们生成的 code_object 没有 __name__,co_name 是 <lambda> 或 <listcomp> 这类占位符,且不绑定到模块命名空间。直接传给 dis.dis() 没问题,但若试图从 globals() 查找会失败。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
dis.dis(lambda x: x + 1)直接反汇编,别尝试dis.dis(globals()['<lambda>'])</lambda>—— 它根本不存在 - 列表推导式本质是函数调用,其字节码在
LOAD_CONST后的co_consts里,需提取后单独dis.dis(const) - 注意 Python 3.12 起,某些简单 comprehension 可能被优化为 C 级循环,不再生成独立
code_object
如何对比不同写法的字节码差异?
关键不是看指令数量,而是关注 LOAD_GLOBAL、CALL_FUNCTION、栈操作深度和跳转频率 —— 这些直接影响执行开销。比如 a if cond else b 和 cond and a or b 表面等价,但后者多一次布尔求值和短路判断。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
dis.Bytecode(func).dis()获取标准化字符串,再用diff工具比对 - 避免依赖
co_firstlineno对齐:不同写法可能导致源码行号偏移,应以指令序列本身为准 - 禁用优化(启动时加
-O0)防止STORE_FAST被合并或常量折叠干扰分析
dis 无法显示 C 扩展或内置函数的实现逻辑
dis.dis(len) 或 dis.dis(print) 只会输出 CALL_FUNCTION 和一堆 POP_TOP,因为内置函数没有 Python 字节码 —— 它们由 C 实现,解释器直接调用底层函数指针。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 想看内置行为,得查 CPython 源码中对应函数(如
Objects/listobject.c里的list_append) - 用
sys.settrace()配合dis可观察调用进出,但无法穿透到 C 层 - 对第三方扩展(如 NumPy),
dis同样失效;此时需结合cProfile或line_profiler定位热点
真正难的不是反汇编本身,而是把字节码指令、栈状态、常量表和局部变量名三者动态对齐 —— 尤其当涉及闭包、生成器或协程时,co_freevars 和 co_cellvars 的映射很容易被忽略。


















