在 Python 中,访问局部变量的速度远快于访问全局变量或对象属性。这是由变量查找机制决定的:局部变量在函数编译时就已经确定了索引位置,运行时只需从当前栈帧的局部变量数组中通过下标直接读取,时间复杂度为 O(1) 且是 C 级别的操作。而全局变量需要经过字典查找,属性访问则需要经历描述符协议或实例字典查找,开销明显更大。
实用做法:将频繁使用的外部对象“缓存”到局部变量中。
典型场景一:循环中反复使用某个函数或方法
# 较慢:每次循环都从 math 模块的全局作用域查找 sqrt 函数
import math
def compute_slow():
result = []
for i in range(100000):
result.append(math.sqrt(i))
return result
# 更快:将 sqrt 赋值给局部变量,循环内直接使用
def compute_fast():
result = []
sqrt = math.sqrt # 局部变量缓存
for i in range(100000):
result.append(sqrt(i))
return result
典型场景二:循环内反复访问对象的属性或方法
# 较慢:每次循环都要通过 self.some_attribute 进行属性查找
def process(items):
for item in items:
if item.status == self.active_status:
self.handler.handle(item)
# 更快:将属性和方法提前取出
def process(items):
active = self.active_status
handle = self.handler.handle
for item in items:
if item.status == active:
handle(item)
这种优化在小规模代码中无关紧要,但在处理百万级循环或数据分析的热路径代码中,性能提升非常明显(实测可提速 10%~30%),且改动成本很低。
属性访问优化
属性访问是 Python 中最常见的操作之一,同时也是许多隐藏性能损耗的来源。以下是一些简单实用的优化方向。
1. 减少不必要的属性查找
在函数内部,如果同一个属性的值不变,就不要重复访问它。原理与局部变量优化相同——将属性值取出保存为局部变量。
# 不推荐:每次循环都要从 self.config 字典中查找 'timeout' 值
for task in tasks:
run(task, timeout=self.config['timeout'])
# 推荐:提前取出
timeout = self.config['timeout']
for task in tasks:
run(task, timeout=timeout)
2. 使用 slots 减少实例内存开销与访问成本
默认情况下,每个类的实例用一个 dict 字典来存储属性,这带来了灵活性,但也增加了内存占用和属性查找时间。如果类的属性集合是固定的(例如数据类、大量小对象),可以通过定义 slots 来禁用 dict,转而使用更紧凑的内部数组存储属性。这样属性访问就变成了类似局部变量的数组下标访问,速度更快、内存更省。
class Point:
__slots__ = ('x', 'y') # 不再创建 __dict__
def __init__(self, x, y):
self.x = x
self.y = y
注意:slots 会失去动态添加属性的能力,仅适合属性固定的场景。
3. 缓存计算量大的属性结果
对于一些需要复杂计算、但多次访问时结果不变的属性,可以使用 functools.cached_property 将计算结果缓存到实例的 dict 中,后续访问直接返回缓存值。
from functools import cached_property
class DataReport:
@cached_property
def summary(self):
# 耗时计算,只执行一次
return heavy_computation(self.raw_data)
首次访问 summary 时会执行函数并将结果存入实例字典,之后直接返回该值。这比手动在 init 中计算要好,因为它实现了“按需计算 + 缓存”。
4. 注意属性访问触发的隐式操作
Python 的 @property 装饰器或自定义 getattr 会让看起来简单的属性访问背后执行函数调用,如果在循环中频繁使用也会引入额外开销。对这类属性,同样建议用局部变量先取值,避免循环内反复触发方法调用。
这些优化都遵循一个原则:不要让同一件事被重复做太多遍。在 Python 中,一次普通的属性访问可能只有几十纳秒,但乘上十万、百万次循环,就会成为可感知的性能瓶颈。