dis查看字节码
从 print(‘hello’) 开始:用 dis 验证你的 Python 直觉
你在终端敲下 python -m dis "print('hello')",看到几行 LOAD_GLOBAL、PUSH_NULL、CALL 之类的输出,大概会点头:“哦,原来 print 是这样执行的。”然后呢?
这串字节码真正的价值,不在于告诉你 print 怎么跑,而在于它提供了一把可重复验证的尺子。比如你可以立刻追问:如果把 print(‘hello’) 换成 sys.stdout.write(‘hello\n’),哪条指令会消失?字符串 ‘hello’ 是每次调用都重新加载,还是被缓存了?函数名 print 的查找开销到底占多少?这些问题靠读文档或猜是得不到确定答案的,但 dis 能在毫秒级给出可复现的证据。
本文不打算系统讲解 CPython 虚拟机,那是另一个话题。我们只把 dis 当作诊断工具,用一系列“直觉猜测 → dis 验证 → 修正认知”的小实验,解决那些代码看起来没问题、行为却让人困惑的瞬间。所有示例基于 CPython 3.12+,字节码格式在此版本后有较大调整,若你使用更早版本,部分输出可能不一致,建议升级后再跟随操作。
字符串拼接的字节码真相
“f-string 比 % 和 + 拼接都快。”这句话你大概听过无数遍,甚至已经当作编码规范写进了团队文档。但如果你被问到“快在哪里、快多少、有没有例外”,能给出字节码级别的证据吗?
我们先用三段功能等价的代码作为实验对象:
name = "world"
a = f"Hello {name}"
b = "Hello %s" % name
c = "Hello " + name
不看 dis 输出,凭直觉猜一下:哪段指令更少?哪段隐藏开销更大?很多人会认为 f-string 完全在编译期处理,应该最简洁;% 格式化涉及类型检查和格式解析,应该最重;+ 拼接居中。这个直觉对了一半。
用 dis.dis() 分别查看三者,关键差异如下。f-string 的核心指令序列为: