CodeWalk

Python性能陷阱:属性查找与描述符协议

作者:孤独的心 · 2026-05-30 12:55

请解释Python中属性查找的性能开销和完整流程(描述符协议:__getattribute__ -> __getattr__ -> 数据描述符 -> 实例字典 -> 非数据描述符 -> 类字典)。为什么obj.attr比局部变量慢很多?如何缓存属性查找结果?

回答

孤独的心

属性查找完整流程

# obj.attr 的查找顺序
# 1. type(obj).__mro__ 中查找数据描述符(有 __set__ 或 __delete__)
# 2. obj.__dict__
# 3. type(obj).__mro__ 中查找非数据描述符(有 __get__ 但无 __set__)
# 4. type(obj).__mro__ 中查找普通类属性
# 5. 如果都找不到,调用 obj.__getattr__(attr)(如果定义了)
# 6. 最后抛出 AttributeError

性能开销对比

import timeit

class MyClass:
    def __init__(self):
        self.value = 42

obj = MyClass()
value_local = obj.value  # 局部变量

# 局部变量访问(LOAD_FAST) — 约0.05us
print(timeit.timeit('value_local', globals=locals()))

# 属性访问(LOAD_ATTR) — 约0.15us,慢3倍
print(timeit.timeit('obj.value', globals=locals()))

原因:

  1. LOAD_FAST:直接数组索引(一条字节码指令)
  2. LOAD_ATTR:需要执行完整的属性查找链,涉及多个C函数调用

缓存策略

# 方式1:局部变量缓存(推荐)
def process(items):
    append = items.append  # 缓存方法
    for i in range(1000):
        append(i)  # 减少LOAD_ATTR

# 方式2:cached_property(Python 3.8+)
from functools import cached_property

class Data:
    @cached_property
    def expensive(self):
        return self._compute()

# 方式3:__slots__加速属性访问
class Point:
    __slots__ = ('x', 'y')
    # 避免__dict__查找,属性存储在固定数组中

性能建议: 热点代码中将频繁访问的属性/方法缓存为局部变量。