1. 先从一次普通属性访问开始,搞清 Python 默认查找顺序
__getattr__、__getattribute__、__get__ 这三个方法,在 Python 面试里经常被放在一起问,好像它们是一条链路上的三个关卡。但实际上它们的分工差异非常大,而且调用时机完全不是“依次执行”的关系。如果只是记住结论,遇到真实项目里的组合场景,很容易写出递归爆栈或者行为诡异的代码。所以我一直觉得,与其背“优先级列表”,不如自己动手跑一遍最基础的查找顺序,把 Python 在访问一个实例属性时到底做了什么彻底看明白。
先做一个观念上的铺垫:平时写的 obj.x,并不是简单地从 obj.__dict__ 里取一个键。真正被调用的,是 type(obj) 上继承下来的 __getattribute__ 方法。默认情况下它由 object 提供,而它内部会依次处理类属性、数据描述符、实例字典、非数据描述符等对象。只有这一整套默认查找都失败并抛出 AttributeError 时,解释器才会和 __getattr__ 打交道。
1.1 obj.x 背后到底走多少步
我见过很多同学直接背这样一段结论:数据描述符优先于实例字典,实例字典优先于非数据描述符,最后才轮到 __getattr__。这个结论在多数场景下没问题,但真正去读一段带描述符的代码时,还是会犯晕,因为描述符本身也是类属性,可它又会影响属性访问的最终结果。
下面这个例子是最直观的验证方式。我定义两个类属性,一个是数据描述符,另一个是非数据描述符,然后在 __init__ 里强行往实例字典塞同名键:
python复制class DataDesc:
def __get__(self, instance, owner):
return "data descriptor value"
def __set__(self, instance, value):
print("DataDesc.__set__ called:", value)
class NonDataDesc:
def __get__(self, instance, owner):
return "non-dat
