Python性能陷阱:属性查找与描述符协议
请解释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()))
原因:
LOAD_FAST:直接数组索引(一条字节码指令)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__查找,属性存储在固定数组中
性能建议: 热点代码中将频繁访问的属性/方法缓存为局部变量。