直接回答:CPython 执行分两步:编译器先把源码解析成 AST,再编译成字节码(存在 code object 的 co_code 里,缓存为 __pycache__ 下的 .pyc 文件);然后基于栈的虚拟机逐条取指令执行——操作数压栈、运算、结果写回局部变量数组。dis 模块可以反汇编任何函数或 code object,是理解解释器行为的一手工具。

import dis

def add(a, b):
    return a + b

dis.dis(add)
#  2 LOAD_FAST     0 (a)
#    LOAD_FAST     1 (b)
#    BINARY_OP      0 (+)
#    RETURN_VALUE

展开解析:几个实用观察点。一是看变量的存储位置:局部变量走 LOAD_FAST(数组下标访问,最快),闭包变量走 LOAD_DEREF(经由 cell 对象间接寻址),全局变量走 LOAD_GLOBAL(模块 dict 查找),这解释了为什么函数内频繁访问全局变量更慢,以及闭包为什么能跨作用域共享变量。二是 .pyc 头部带 magic number 和时间戳或源文件哈希,magic 与解释器版本绑定,所以字节码不跨版本兼容,升级 Python 后旧缓存自动作废。三是 3.11 起指令集大改:出现类型特化指令和 CACHE 伪指令占位的内联缓存、统一的 CALL 调用协议,同一段代码在 3.10 和 3.12 下反汇编结果明显不同。

实践要点:code object 上还有 co_consts(常量表)、co_varnames(局部变量名)、co_flags(是否生成器/协程等)。调试诡异行为时——比如闭包捕获循环变量、可变默认参数共享——直接看字节码比猜快得多。

追问方向:为什么函数调用比内联代码慢(帧创建与销毁的开销)?cell 对象是如何让闭包变量在内外层之间保持同步的?

(约 560 字)