全部学科
Python全栈
python
NodeJS全栈
nodejs
📅 2026-05-19 8 分钟 ✍️ juanwangdev

Python属性查找链

Python属性查找遵循严格的链式顺序,理解这一机制对掌握元编程至关重要。

查找顺序

属性查找按以下优先级进行:

  1. 数据描述符(类中定义了__get____set__的描述符)
  2. 实例字典instance.__dict__
  3. 非数据描述符(仅定义__get__的描述符)
  4. 类字典type(instance).__dict__
  5. 基类字典(沿MRO链查找)

核心机制演示

Python
# 描述符类型对比
class DataDescriptor:
    "数据描述符:优先级高于实例字典"
    def __get__(self, obj, owner):
        return f"DataDescriptor.get from {obj}"

    def __set__(self, obj, value):
        print(f"DataDescriptor.set: {value}")

class NonDataDescriptor:
    "非数据描述符:优先级低于实例字典"
    def __get__(self, obj, owner):
        return f"NonDataDescriptor.get from {obj}"

class Demo:
    data_desc = DataDescriptor()
    non_data_desc = NonDataDescriptor()
    class_attr = "class value"

# 测试查找顺序
d = Demo()

# 数据描述符优先于实例字典
d.__dict__['data_desc'] = "instance override"
print(d.data_desc)  # 输出: DataDescriptor.get from <Demo object>

# 实例字典优先于非数据描述符
d.__dict__['non_data_desc'] = "instance override"
print(d.non_data_desc)  # 输出: instance override

MRO与基类查找

Python
class A:
    attr = "A"

class B(A):
    pass

class C(A):
    attr = "C"

class D(B, C):
    pass

# MRO决定基类查找顺序
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
print(D.attr)  # 输出: C(按MRO顺序,C在A之前)

__getattr____getattribute__

Python
class CompleteLookup:
    def __getattribute__(self, name):
        "拦截所有属性访问"
        print(f"__getattribute__ called for {name}")
        return super().__getattribute__(name)

    def __getattr__(self, name):
        "仅在属性不存在时调用"
        print(f"__getattr__ called for {name}")
        return f"fallback: {name}"

obj = CompleteLookup()
obj.existing = "value"
print(obj.existing)  # __getattribute__ → 返回 "value"
print(obj.missing)   # __getattribute__ → __getattr__ → "fallback: missing"

注意:__getattribute__极易造成无限递归,必须用super().__getattribute__()访问属性。

完整查找流程图

text
属性访问 obj.attr
        ↓
┌───────────────────────────────┐
│ __getattribute__(可拦截)      │
└───────────────────────────────┘
        ↓
  类中是否有数据描述符?
        ↓是                    ↓否
返回描述符.__get__()     检查实例.__dict__
                              ↓
                        找到? → 返回值
                              ↓未找到
                     类中是否有非数据描述符?
                              ↓是          ↓否
                     返回描述符.__get__()  检查类.__dict__
                                              ↓
                                        沿MRO查找基类
                                              ↓
                                          找到?→返回
                                              ↓未找到
                                        调用__getattr__()

要点总结

  1. 数据描述符优先级最高,覆盖实例属性
  2. 实例字典优先于非数据描述符和类属性
  3. MRO决定基类查找顺序(C3线性化算法)
  4. **__getattribute__**拦截所有访问,__getattr__仅处理缺失属性
  5. 描述符必须定义在类层级,实例层级无效
想在手机上练习这篇文章的配套题目?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码
← 上一篇 Python解释器启动流程
下一篇 → Python命名空间与作用域实现
扫码体验小程序
加载中
想在手机上刷题学习?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码