dis.dis()只显示静态字节码快照,不反映执行频次、栈开销或热点路径;需结合co_consts提取嵌套代码、用__wrapped__绕过装饰器,并通过LOAD_GLOBAL频次、局部缓存及timeit验证优化效果。

为什么 dis.dis() 看起来没报错却看不出性能问题?
因为 dis.dis() 默认只展开顶层函数,不递归进入嵌套作用域或生成器表达式;它也不标注执行频次、栈操作开销或常量加载路径。你看到的是一份“静态快照”,不是运行时热点图。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 对疑似低效的函数,先用
dis.dis(func)查看整体指令流,重点关注重复出现的LOAD_GLOBAL、LOAD_ATTR、BINARY_SUBSCR—— 它们往往对应 Python 解释器里较重的查找逻辑 - 若函数含
for循环或列表推导式,需手动提取其内部代码对象:比如func.__code__.co_consts里找code对象,再对它调用dis.dis() - 避免直接分析装饰器包装后的函数(如
@lru_cache),先用func.__wrapped__获取原始函数再 dis
怎样识别 LOAD_GLOBAL 过多导致的性能瓶颈?
每次 LOAD_GLOBAL 都要查模块字典,比局部变量访问慢 3–5 倍。常见于循环内反复调用 len()、range()、math.sqrt() 或未缓存的模块属性。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在 dis 输出中搜索
LOAD_GLOBAL指令行,数它在循环体内的出现频次;若同一名字(如len)每轮都出现,说明没做局部化 - 对比优化前后字节码:把
len = len提到函数开头,再 dis,会发现该位置变成LOAD_FAST—— 这才是局部变量访问指令 - 注意
from math import sqrt后调用sqrt(x)仍是LOAD_GLOBAL,因为导入只是绑定名字,不是声明局部变量;必须显式写sqrt = sqrt才能降级为LOAD_FAST
为什么 list.append() 在循环里反复调用会触发 LOAD_METHOD + CALL_METHOD?
Python 3.7+ 对方法调用做了优化,但每次循环仍需重新绑定方法对象。如果 append 出现在热循环中,这个开销可测(尤其配合大量小对象时)。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- dis 输出中看到成对的
LOAD_METHOD和CALL_METHOD,且紧邻循环指令(如FOR_ITER→LOAD_METHOD),就是信号 - 改用局部变量缓存:在循环外写
append = my_list.append,循环内只调用append(item);dis 会显示LOAD_FAST+CALL_FUNCTION,少一次属性查找 - 注意:缓存后若列表被其他线程修改,可能引发
AttributeError(因方法对象绑定失效),纯单线程场景才推荐此优化
使用 dis.get_instructions() 做自动化扫描要注意什么?
它返回生成器,适合写脚本批量检查多个函数,但容易忽略作用域嵌套和编译期常量折叠。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 别只过滤指令名,要结合
inst.argval判断目标:比如inst.opname == "LOAD_GLOBAL" and inst.argval in ["print", "json.dumps"] - 跳过
co_consts中的None、True等内置常量,它们不会产生实际开销;重点盯住字符串、函数名、模块名 - 无法检测 C 扩展调用(如
numpy.array()),这些在字节码里只体现为一个CALL_FUNCTION,内部耗时完全不可见 —— 此时需配合cProfile定位
真正难的不是看懂某条 LOAD_ATTR,而是判断它是否落在高频路径上;dis 给的是“发生了什么”,不是“值不值得改”。上线前最好用真实数据集跑一遍 timeit,确认优化后字节码变化确实带来可观延迟下降。

















