人人都会AI编程

局部变量优化、属性访问优化

更新时间:2026-07-12

在 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 中,一次普通的属性访问可能只有几十纳秒,但乘上十万、百万次循环,就会成为可感知的性能瓶颈。