Python底层访问对象及类属性的具体执行顺序是怎样的?
Python属性访问完整执行流程
你的现有认知大部分符合Python的实际逻辑,但存在一处关键顺序偏差,也遗漏了非数据描述符、类属性访问的特殊规则,下面是精确到步骤的执行逻辑:
实例属性访问(obj.attr形式)的标准流程
所有实例属性访问的统一入口是实例所属类的__getattribute__方法(默认由顶层基类object提供实现,自定义重写时如果要保留默认查找逻辑需要调用super().__getattribute__,否则极易出现递归死循环),默认实现的查找顺序严格按以下优先级从高到低执行:
- 查找类MRO链上的数据描述符
沿着类的方法解析顺序(MRO,即类继承关系的线性化顺序)遍历所有父类和当前类的__dict__,查找是否存在名为attr的属性:- 如果找到的对象是数据描述符(即同时实现了
__get__和__set__/__delete__方法的对象,内置的property就是最常见的数据描述符),直接调用该描述符的__get__方法返回结果,终止整个查找流程。
这是你之前认知需要修正的核心点:数据描述符的查找早于实例字典检查,不存在「先查对象字典再查类字典」的通用规则,数据描述符可以完全覆盖实例字典里的同名属性。
- 如果找到的对象是数据描述符(即同时实现了
- 查找实例自身的属性字典
如果上一步没有找到匹配的数据描述符,直接访问当前实例的__dict__,如果字典中存在attr键,直接返回对应的值,终止查找。 - 查找类MRO链上的非数据描述符和普通类属性
如果实例字典中没有找到目标属性,再次沿着MRO链遍历所有类的__dict__:- 如果找到的是非数据描述符(仅实现了
__get__方法,没有实现__set__/__delete__,普通实例方法、类方法、静态方法都属于这类),调用其__get__方法返回绑定后的方法对象,终止查找 - 如果找到的是普通类属性(不是描述符对象),直接返回属性值,终止查找
- 如果找到的是非数据描述符(仅实现了
- 触发
__getattr__兜底逻辑
如果以上三步都没有找到目标属性,或者查找过程中任意一步抛出了AttributeError异常,Python会检查当前类是否定义了__getattr__方法:- 如果定义了,调用
__getattr__方法并传入属性名,返回该方法的执行结果 - 如果没有定义,直接抛出
AttributeError异常
- 如果定义了,调用
可以用两段简单代码验证优先级规则:
# 数据描述符优先级高于实例字典 class DataDesc: def __get__(self, instance, owner): return "数据描述符返回值" def __set__(self, instance, value): pass class Test1: attr = DataDesc() t1 = Test1() t1.__dict__["attr"] = "实例字典自定义值" print(t1.attr) # 输出:数据描述符返回值
# 非数据描述符优先级低于实例字典 class NonDataDesc: def __get__(self, instance, owner): return "非数据描述符返回值" class Test2: attr = NonDataDesc() t2 = Test2() print(t2.attr) # 输出:非数据描述符返回值 t2.__dict__["attr"] = "实例字典自定义值" print(t2.attr) # 输出:实例字典自定义值
类属性访问(Class.attr形式)的特殊逻辑
类本身也是元类(默认是type)实例化出来的对象,因此它的属性查找逻辑和实例属性逻辑完全一致,只是查找层级上移了一层:
- 首先调用元类的
__getattribute__方法 - 先查找元类MRO链上的数据描述符
- 再查找类自身的
__dict__ - 再查找元类MRO链上的非数据描述符和普通元类属性
- 都没找到或抛出
AttributeError时,调用元类定义的__getattr__(如果存在),否则抛异常
常见认知误区
- 误区:所有场景都遵循「对象字典优先级高于类字典」。该规则仅在没有匹配数据描述符的前提下成立,数据描述符可以越过实例字典优先生效。
- 误区:只有属性完全不存在时才会触发
__getattr__。只要查找流程中任意环节(包括描述符的__get__方法、property的getter函数)抛出AttributeError,都会触发__getattr__兜底,和属性是否存在无关。 - 误区:非数据描述符和普通类属性优先级不同。二者在查找时属于同一层级,谁在MRO链上更靠前就返回谁,非数据描述符只是会被额外调用
__get__做绑定处理,优先级和普通类属性没有区别。
内容的提问来源于stack exchange,提问作者Matvey_coder3
相关产品推荐
相关产品推荐

