局部变量访问比字段快,因前者用load_fast指令按固定索引取值(o(1)数组寻址),后者需字典哈希查找(含哈希计算、桶定位、键比对);实测self.x比局部变量慢2–5倍。

局部变量访问比字段(成员变量)快,核心不在“栈快堆慢”的笼统说法,而在名字查找机制和字节码指令路径的根本差异。
局部变量走的是固定索引,不查名字
Python 编译函数时就确定每个局部变量在栈帧中的位置。运行时用 LOAD_FAST 指令,直接按数字索引取值,比如第 0 个、第 1 个——没有哈希、没有键比对、没有作用域跳转。
这相当于数组下标访问,是纯内存寻址,常数时间 O(1),且 CPU 缓存友好。
字段访问本质是字典查找
类的实例字段(如 self.x)存储在对象的 __dict__ 字典里。每次访问都要:
- 计算字符串
'x'的哈希值 - 在哈希表中定位桶(bucket)
- 遍历桶内可能的冲突项,逐个比对键名
即使平均是 O(1),实际开销远高于数组索引。实测密集循环中,self.x 访问比等价局部变量慢 2–5 倍。
别让局部变量“掉队”
以下写法会让本该快速的局部变量退化为慢速查找:
- 函数内先读同名变量再赋值(触发
UnboundLocalError或隐式全局引用) - 用了
exec()、eval()或locals() - 加了
global或nonlocal声明——哪怕只读,也强制走LOAD_GLOBAL或LOAD_DEREF
用 dis.dis(func) 能清楚看到:局部变量对应 LOAD_FAST,字段访问对应 LOAD_ATTR,全局变量对应 LOAD_GLOBAL。
实用优化建议
不是所有地方都值得抠这点性能,但高频路径上效果明显:
- 循环内频繁访问的字段,提前赋给局部变量,例如:
x = self.x再用x - 调用多次的方法,也建议提取,比如:
append = mylist.append,再在循环里用append(item) - 配置常量不要反复点
config.TIMEOUT,一次赋值:timeout = config.TIMEOUT - 避免在热循环里做属性访问或点号操作,尤其是带 descriptor 或
__get__的自定义属性
这些改动几乎零成本,可读性不变,却能稳定带来 10%~25% 的纯计算加速。











