属性查找顺序:实例 → 类 → 父类的完整 MRO

文章目录

    • 一个让人困惑的现象
    • [第一部分:`object.getattribute` 的完整执行路径](#第一部分:object.__getattribute__ 的完整执行路径)
      • [关键区分:数据描述符 vs 非数据描述符](#关键区分:数据描述符 vs 非数据描述符)
    • [第二部分:MRO 的本质------C3 线性化算法](#第二部分:MRO 的本质——C3 线性化算法)
    • [第三部分:`super()` 的真实工作方式](#第三部分:super() 的真实工作方式)
      • [`super()` 不是"调用父类"](#super() 不是"调用父类")
      • [`super()` 的参数形式](#super() 的参数形式)
      • [协作继承(Cooperative Multiple Inheritance)](#协作继承(Cooperative Multiple Inheritance))
    • [第四部分:`getattr` vs `getattribute` 的分工](#第四部分:__getattr__ vs __getattribute__ 的分工)
    • [第五部分:实例 `dict` 的内部结构与生命周期](#第五部分:实例 __dict__ 的内部结构与生命周期)
      • [`dict` 是什么](#__dict__ 是什么)
      • [实例 `dict` vs 类 `dict`:写操作的差异](#实例 __dict__ vs 类 __dict__:写操作的差异)
    • 第六部分:多重继承的属性查找陷阱与工程实践
      • [Mixin 设计模式与 MRO](#Mixin 设计模式与 MRO)
      • [MRO 冲突的常见场景](#MRO 冲突的常见场景)
      • [动态修改 MRO 的方式](#动态修改 MRO 的方式)
    • [第七部分:工程场景全实战------Django 风格 ORM 字段的 MRO 应用](#第七部分:工程场景全实战——Django 风格 ORM 字段的 MRO 应用)
    • 关键结论
    • 小结

一个让人困惑的现象

python 复制代码
class A:
    x = "class A"

class B(A):
    pass

b = B()
b.x = "instance B"

print(b.x)       # instance B
print(B.x)       # class A
del b.x
print(b.x)       # class A  ← 删除实例属性后,回退到类属性

这段代码中,b.x 的值随着操作不断变化------不是因为数据变了,而是查找路径在变化。每次访问属性,Python 都在执行一套精确的查找算法,只是通常无需在意这套机制。

一旦涉及多重继承、描述符、元类,这套机制就变得至关重要。诊断一个"属性怎么不见了"的 Bug、理解 Django Model 字段为何能跨类复用、解释 super() 调用时究竟去哪找方法------所有问题的答案都在同一个地方:属性查找顺序(Attribute Lookup Order)方法解析顺序(MRO, Method Resolution Order)


第一部分:object.__getattribute__ 的完整执行路径

Python 中的属性访问 obj.attr 实际上触发的是 type(obj).__getattribute__(obj, "attr"),由 object.__getattribute__ 提供默认实现。

这个方法执行的逻辑是固定的,用伪代码描述如下:

python 复制代码
def __getattribute__(obj, name):
    # 步骤 1:沿 MRO 在类层次中查找 name
    for cls in type(obj).__mro__:
        if name in cls.__dict__:
            descriptor = cls.__dict__[name]
            # 步骤 2:若是数据描述符,优先执行 __get__
            if hasattr(descriptor, '__get__') and (
                hasattr(descriptor, '__set__') or hasattr(descriptor, '__delete__')
            ):
                return descriptor.__get__(obj, type(obj))
            break  # 找到了,但不是数据描述符,先记下来

    # 步骤 3:查找实例 __dict__
    if name in obj.__dict__:
        return obj.__dict__[name]

    # 步骤 4:再次检查类层次(非数据描述符 or 普通类属性)
    for cls in type(obj).__mro__:
        if name in cls.__dict__:
            attr = cls.__dict__[name]
            if hasattr(attr, '__get__'):
                return attr.__get__(obj, type(obj))
            return attr

    raise AttributeError(f"'{type(obj).__name__}' object has no attribute '{name}'")

整个查找优先级是一条固定链路:

复制代码
数据描述符(类层次)> 实例 __dict__ > 非数据描述符 or 普通类属性 > AttributeError

这条优先级链路是 Python 面向对象体系的基石,下面用一张图说明完整执行过程:








obj.attr 触发

type(obj).getattribute(obj, 'attr')
沿 MRO 扫描类层次

寻找 attr
在某个类 dict

中找到了?
AttributeError
是数据描述符?

同时有 getset/delete
调用 descriptor.get(obj, type(obj))

✓ 优先级最高,直接返回
记录为候选,继续向下
实例 obj.dict

中有 attr?
返回 obj.dict['attr']

✓ 实例属性优先于非数据描述符
候选是否有 get

即非数据描述符?
调用 candidate.get(obj, type(obj))

典型:方法绑定
直接返回类属性值
返回结果
抛出 AttributeError

关键区分:数据描述符 vs 非数据描述符

类型 条件 优先级 典型例子
数据描述符 __get__ + 有 __set____delete__ 高于实例 __dict__ propertyclassmethod(内部)、ORM 字段
非数据描述符 只有 __get__ 低于实例 __dict__ 函数对象(方法)、cached_property(未触发前)
普通类属性 无描述符协议方法 低于实例 __dict__ 类级常量、类变量

这一区分解释了一个重要行为:

python 复制代码
class Foo:
    @property
    def x(self):           # property 是数据描述符(有 __set__,会触发 AttributeError)
        return 42

foo = Foo()
foo.__dict__['x'] = 99     # 直接向实例 __dict__ 写入

print(foo.x)               # 仍然是 42!数据描述符优先级更高,实例 __dict__ 被绕过

而函数(非数据描述符)则不同:

python 复制代码
class Bar:
    def greet(self):
        return "from class"

bar = Bar()
bar.greet = lambda: "from instance"   # 实例属性覆盖了类方法

print(bar.greet())   # from instance  ← 实例 __dict__ 优先于非数据描述符

第二部分:MRO 的本质------C3 线性化算法

属性查找沿 MRO 进行,MRO 本身由C3 线性化算法计算得出。

为什么不能用深度优先

Python 2 的旧式类(classic class)使用深度优先搜索(DFS)确定 MRO,在菱形继承场景下会产生问题:

python 复制代码
class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass

菱形继承结构如下:

复制代码
    A
   / \
  B   C
   \ /
    D

DFS 的遍历顺序是 D → B → A → C,这意味着 A.method 会在 C.method 之前被找到,导致 C 对 A 的重写对 D 完全无效------这违背了继承的语义预期。

C3 线性化解决了这个问题,保证了三个约束:

  1. 子类优先:子类总是在父类前面
  2. 本地声明优先class D(B, C) 中 B 在 C 前,这个顺序被保留
  3. 单调性:不破坏父类自身的 MRO 顺序

C3 算法的公式:

复制代码
L[C(B1...BN)] = C + merge(L[B1], L[B2], ..., L[BN], [B1, B2, ..., BN])

merge 操作规则:取第一个序列的头部,检查它是否出现在其他序列的尾部(非头部),若否则加入结果并从所有序列中删除,重复直到所有序列清空。

D(B, C) 计算 MRO:

复制代码
L[D] = D + merge(L[B], L[C], [B, C])
     = D + merge([B, A, object], [C, A, object], [B, C])
     # 取 B:B 不在任何序列尾部 → 加入,删除 B
     = D + B + merge([A, object], [C, A, object], [C])
     # 取 A:A 在 [C, A, object] 的尾部 → 跳过;取 C:C 不在任何尾部 → 加入
     = D + B + C + merge([A, object], [A, object], [])
     # 取 A:加入
     = D + B + C + A + object

验证:

python 复制代码
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

当 C3 失败时

python 复制代码
class X:
    pass

class Y(X):
    pass

# 下面这个会引发 TypeError
class Z(X, Y):   # X 在 Y 前,但 Y 继承自 X,违反"子类优先"
    pass

# TypeError: Cannot create a consistent method resolution order (MRO)
# for bases X, Y

C3 算法检测到无法构造一致的 MRO 时,会在类创建阶段就直接报错,而不是等到运行时出现行为异常。这是一个安全机制。


第三部分:super() 的真实工作方式

super() 是整个 MRO 机制最常用的接口,但对它的误解也最多。

super() 不是"调用父类"

准确描述是:super() 返回一个代理对象,该对象在 MRO 中当前类的后续位置开始查找属性。

python 复制代码
class A:
    def greet(self):
        return "A"

class B(A):
    def greet(self):
        base = super().greet()   # 在 B 之后的 MRO 查找 greet,找到 A
        return f"B → {base}"

class C(A):
    def greet(self):
        base = super().greet()   # 在 C 之后的 MRO 查找 greet,找到 A
        return f"C → {base}"

class D(B, C):
    def greet(self):
        base = super().greet()   # 在 D 之后的 MRO 查找:B、C、A...
        return f"D → {base}"

D.__mro__[D, B, C, A, object]D.greet() 中的 super() 从 B 开始查找,触发了完整的协作调用链:

python 复制代码
d = D()
print(d.greet())
# D → B → C → A

每个 super().greet() 都不是直接调用"我的父类",而是 MRO 中的下一个------B 的 super() 找到的是 C,而不是 A。

super() 的参数形式

python 复制代码
# 现代 Python(3.x):无参数,编译器自动注入 __class__ cell
super()

# 显式形式(适用于需要明确指定的场景)
super(ClassName, self)   # 从 MRO 中 ClassName 之后开始查找,绑定 self

# 类方法中
super(ClassName, cls)

# 无绑定(返回未绑定超类代理,通常不需要)
super(ClassName)

super() 的无参形式依赖一个隐式 __class__ cell 变量,由编译器在函数定义阶段注入。这意味着在动态修改 __class__ 的场景下,无参 super() 可能产生意外行为,此时需要改用显式形式。

协作继承(Cooperative Multiple Inheritance)

super() 的设计使得协作继承 成为可能:每个中间类都通过 super() 把控制权传递给 MRO 中的下一个类,最终所有类的方法都能被调用到。

但协作继承有一个前提:MRO 中所有类都必须调用 super() 。如果链条中的某个类不调用 super(),后续类将被跳过:

python 复制代码
class A:
    def setup(self):
        print("A.setup")

class B(A):
    def setup(self):
        print("B.setup")
        # 没有 super()!A.setup 被截断

class C(A):
    def setup(self):
        print("C.setup")
        super().setup()

class D(B, C):
    def setup(self):
        print("D.setup")
        super().setup()

# MRO: D → B → C → A
D().setup()
# D.setup
# B.setup      ← B 没有 super(),C 和 A 都被截断

这个场景在接入第三方类库时尤为常见:如果第三方类的 __init__ 没有调用 super().__init__(),混入(Mixin)类的初始化代码将被跳过。


第四部分:__getattr__ vs __getattribute__ 的分工

Python 提供了两个"拦截属性访问"的钩子方法,它们的触发时机完全不同:

方法 触发时机 常见用途
__getattribute__ 每次属性访问都触发,在正常查找之前 完全接管属性访问逻辑
__getattr__ 仅在正常查找失败(AttributeError)时触发 动态属性、懒加载、代理对象

__getattr__:优雅的兜底机制

python 复制代码
class DynamicConfig:
    """
    一个从环境变量读取配置的类,支持 config.database_host 这样的访问方式。
    """
    _prefix = "APP_"

    def __init__(self):
        self._cache = {}

    def __getattr__(self, name: str):
        # 仅在找不到属性时触发
        env_key = self._prefix + name.upper()
        import os
        value = os.environ.get(env_key)
        if value is None:
            raise AttributeError(f"配置项 '{name}' 未找到(环境变量 {env_key} 未设置)")
        self._cache[name] = value
        return value

config = DynamicConfig()
# 访问 config.database_host → 查找 APP_DATABASE_HOST 环境变量
# 找不到 → AttributeError(而不是 KeyError 或 None)

__getattr__ 的优雅之处在于不打扰正常属性访问self._cacheself._prefix 的访问不会触发它,因为这些属性存在于实例 __dict__ 和类 __dict__ 中。

__getattribute__:危险的全局拦截

重写 __getattribute__ 意味着拦截所有属性访问 ,包括 self.xxx 调用本身。稍不注意,就会导致无限递归:

python 复制代码
class Proxy:
    def __getattribute__(self, name):
        # 危险:self._target 本身也是一次属性访问,触发无限递归!
        return getattr(self._target, name)

# 正确写法:通过 object.__getattribute__ 访问代理对象自身的属性
class SafeProxy:
    def __init__(self, target):
        object.__setattr__(self, '_target', target)   # 绕过自定义的 __setattr__

    def __getattribute__(self, name):
        if name.startswith('_'):
            # 代理对象自身的私有属性,通过 object.__getattribute__ 访问
            return object.__getattribute__(self, name)
        target = object.__getattribute__(self, '_target')
        return getattr(target, name)

访问链路全景





obj.attr
type(obj).getattribute(obj, 'attr')

(默认为 object.getattribute
标准查找路径

数据描述符 → 实例__dict__ → 非数据描述符
找到了?
返回属性值
抛出 AttributeError
类层次中

getattr
调用 getattr(obj, 'attr')

兜底处理,可返回值或再次抛异常
AttributeError 向上传播
返回 getattr 的结果


第五部分:实例 __dict__ 的内部结构与生命周期

__dict__ 是什么

每个普通 Python 对象都持有一个实例字典 __dict__,用于存储实例级别的属性:

python 复制代码
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(3, 4)
print(p.__dict__)   # {'x': 3, 'y': 4}

p.z = 10            # 直接添加新属性
print(p.__dict__)   # {'x': 3, 'y': 4, 'z': 10}

类的 __dict__ 是一个只读的 mappingproxy 对象,防止直接修改类命名空间:

python 复制代码
print(Point.__dict__)
# mappingproxy({'__module__': '__main__', '__init__': <function ...>, ...})

Point.__dict__['x'] = 5   # TypeError: 'mappingproxy' object does not support item assignment
# 只能通过 setattr(Point, 'x', 5) 修改类属性

实例 __dict__ vs 类 __dict__:写操作的差异

读操作沿 MRO 查找,写操作(setattr)默认只写入实例 __dict__

python 复制代码
class Counter:
    count = 0   # 类属性

c1 = Counter()
c2 = Counter()

c1.count += 1  # 等价于 c1.count = c1.count + 1
               # 读取走 MRO(从类读 0),写入到实例 __dict__

print(c1.count)   # 1  ← 实例属性
print(c2.count)   # 0  ← 类属性(c2 没有实例级别的 count)
print(Counter.count)  # 0  ← 类属性未变

这个行为是 Python 初学者常见的陷阱来源:c1.count += 1 看起来像是修改了类变量,实际上是在实例上创建了一个遮蔽(shadow)类变量的实例变量。

若要修改类变量,必须显式通过类访问:

python 复制代码
Counter.count += 1
print(Counter.count)   # 1
print(c2.count)        # 1  ← 跟随类变量变化(c2 没有实例级 count)
print(c1.count)        # 1  ← c1 有实例级 count(值为 1),与类变量恰好相同

第六部分:多重继承的属性查找陷阱与工程实践

Mixin 设计模式与 MRO

Mixin 是 Python 多重继承的主要应用场景,用于向类注入正交功能:

python 复制代码
import json
import logging


class LogMixin:
    """注入结构化日志能力的 Mixin"""

    def log_info(self, msg: str, **kwargs) -> None:
        extra = json.dumps(kwargs, ensure_ascii=False)
        logging.info(f"[{self.__class__.__name__}] {msg} | {extra}")

    def log_error(self, msg: str, **kwargs) -> None:
        extra = json.dumps(kwargs, ensure_ascii=False)
        logging.error(f"[{self.__class__.__name__}] {msg} | {extra}")


class SerializeMixin:
    """注入 JSON 序列化能力的 Mixin"""

    def to_json(self) -> str:
        return json.dumps(self.__dict__, ensure_ascii=False, default=str)

    @classmethod
    def from_json(cls, json_str: str):
        data = json.loads(json_str)
        obj = cls.__new__(cls)
        obj.__dict__.update(data)
        return obj


class ValidationMixin:
    """注入字段校验能力的 Mixin,子类需要定义 _validators 字典"""

    _validators: dict = {}

    def validate(self) -> list[str]:
        errors = []
        for field, validator in self._validators.items():
            value = getattr(self, field, None)
            try:
                validator(value)
            except (ValueError, TypeError) as e:
                errors.append(f"{field}: {e}")
        return errors


class User(LogMixin, SerializeMixin, ValidationMixin):
    _validators = {
        'name': lambda v: (_ for _ in ()).throw(ValueError("name 不能为空")) if not v else None,
        'age': lambda v: (_ for _ in ()).throw(ValueError("age 必须大于 0")) if not isinstance(v, int) or v <= 0 else None,
    }

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age
        self.log_info("用户创建", name=name, age=age)

MRO 决定了多个 Mixin 的方法解析顺序:

python 复制代码
print(User.__mro__)
# (<class 'User'>, <class 'LogMixin'>, <class 'SerializeMixin'>, <class 'ValidationMixin'>, <class 'object'>)

如果两个 Mixin 定义了同名方法,MRO 中更靠前的类会"赢"。这是 Mixin 排列顺序很重要的原因。

MRO 冲突的常见场景

场景一:两个父类都重写了 __init__,且都需要执行

python 复制代码
class TimestampMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        import time
        self.created_at = time.time()
        self.updated_at = time.time()


class AuditMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.created_by = kwargs.get('user', 'system')


class BaseModel:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)


class Article(TimestampMixin, AuditMixin, BaseModel):
    pass

# MRO: Article → TimestampMixin → AuditMixin → BaseModel → object
# 关键:每个 Mixin 的 __init__ 都调用了 super().__init__(**kwargs)
# 确保了链条不断裂,所有 Mixin 的初始化都会执行

a = Article(title="Hello", user="admin")
print(a.title)       # Hello
print(a.created_at)  # 时间戳
print(a.created_by)  # admin

场景二:__init__**kwargs 传递策略

协作继承中,**kwargs 应该向上传递------否则靠后的类无法接收到它们期望的参数。但当到达 object.__init__ 时,它不接受额外参数(Python 3.x 中 object.__init__(self) 严格不接受其他参数):

python 复制代码
class SafeBase:
    """作为 Mixin 继承链的终点,吸收多余的 kwargs"""
    def __init__(self, **kwargs):
        # 不向上传递,避免 object.__init__() 因意外参数报错
        if kwargs:
            import warnings
            warnings.warn(f"未消费的参数: {list(kwargs.keys())}", stacklevel=2)
        super().__init__()   # 干净地调用 object.__init__

动态修改 MRO 的方式

Python 允许通过元类定制 MRO,但这属于高级场景。更常用的是通过调整继承声明顺序来控制 MRO:

python 复制代码
# 希望 B 的 method 比 C 的 method 先被找到
class D(B, C): pass   # MRO: D → B → C → A

# 希望 C 的 method 比 B 的 method 先被找到
class D(C, B): pass   # MRO: D → C → B → A

还可以通过 __mro_entries__ 协议(PEP 560)实现泛型类型在 class X(Generic[T]) 中的正确 MRO 处理,但这是类型注解系统的内部机制,通常不需要手动操作。


第七部分:工程场景全实战------Django 风格 ORM 字段的 MRO 应用

将前面的知识整合为一个完整的工程案例:一个基于描述符 + MRO 的 ORM 字段系统。

python 复制代码
"""
一个精简的 ORM 字段系统,展示描述符与 MRO 如何协作。
"""
from __future__ import annotations
from typing import Any, Type


class Field:
    """ORM 字段描述符基类(数据描述符)"""

    _field_counter = 0  # 类级计数器,记录字段定义顺序

    def __init__(self, nullable: bool = False, default: Any = None):
        self.nullable = nullable
        self.default = default
        self._order = Field._field_counter
        Field._field_counter += 1
        self._attr_name: str = ''

    def __set_name__(self, owner: type, name: str) -> None:
        """Python 3.6+ 特性:描述符被赋值给类属性时自动调用"""
        self._attr_name = f'_field_{name}'

    def __get__(self, obj: Any, objtype: Type | None = None) -> Any:
        if obj is None:
            return self   # 通过类访问时返回描述符自身
        return obj.__dict__.get(self._attr_name, self.default)

    def __set__(self, obj: Any, value: Any) -> None:
        if value is None and not self.nullable:
            raise ValueError(f"字段 '{self._attr_name}' 不允许为 None")
        obj.__dict__[self._attr_name] = self.validate(value)

    def __delete__(self, obj: Any) -> None:
        obj.__dict__.pop(self._attr_name, None)

    def validate(self, value: Any) -> Any:
        """子类重写以添加类型验证"""
        return value


class IntField(Field):
    def __init__(self, min_val: int | None = None, max_val: int | None = None, **kwargs):
        super().__init__(**kwargs)
        self.min_val = min_val
        self.max_val = max_val

    def validate(self, value: Any) -> int:
        if value is None:
            return value
        value = int(value)
        if self.min_val is not None and value < self.min_val:
            raise ValueError(f"值 {value} 小于最小值 {self.min_val}")
        if self.max_val is not None and value > self.max_val:
            raise ValueError(f"值 {value} 大于最大值 {self.max_val}")
        return value


class StrField(Field):
    def __init__(self, max_length: int = 255, strip: bool = True, **kwargs):
        super().__init__(**kwargs)
        self.max_length = max_length
        self.strip = strip

    def validate(self, value: Any) -> str:
        if value is None:
            return value
        s = str(value)
        if self.strip:
            s = s.strip()
        if len(s) > self.max_length:
            raise ValueError(f"长度 {len(s)} 超过最大值 {self.max_length}")
        return s


class ModelMeta(type):
    """元类:收集字段定义,构建 _fields 映射"""

    def __new__(mcs, name: str, bases: tuple, namespace: dict):
        fields = {}
        # 继承父类的字段(沿 MRO 收集)
        for base in reversed(bases):
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        # 收集本类新定义的字段
        for attr, value in namespace.items():
            if isinstance(value, Field):
                fields[attr] = value
        namespace['_fields'] = fields
        return super().__new__(mcs, name, bases, namespace)


class Model(metaclass=ModelMeta):
    """ORM 模型基类"""

    def __init__(self, **kwargs):
        for field_name in self._fields:
            value = kwargs.get(field_name, self._fields[field_name].default)
            setattr(self, field_name, value)

    def __repr__(self) -> str:
        attrs = ', '.join(
            f"{k}={getattr(self, k)!r}"
            for k in self._fields
        )
        return f"{self.__class__.__name__}({attrs})"

    def validate_all(self) -> dict[str, str]:
        """返回所有校验错误"""
        errors = {}
        for field_name, field in self._fields.items():
            try:
                value = getattr(self, field_name)
                field.validate(value)
            except ValueError as e:
                errors[field_name] = str(e)
        return errors


# ── 具体模型定义

class TimestampMixin(Model):
    """提供 created_at / updated_at 字段的 Mixin"""
    created_at = StrField(max_length=30, nullable=True)
    updated_at = StrField(max_length=30, nullable=True)


class UserBase(Model):
    id = IntField(min_val=1)
    name = StrField(max_length=50)
    age = IntField(min_val=0, max_val=150, nullable=True)


class Employee(TimestampMixin, UserBase):
    """
    MRO: Employee → TimestampMixin → UserBase → Model → object
    _fields 包含来自 TimestampMixin 和 UserBase 的所有字段
    """
    department = StrField(max_length=100, nullable=True)
    salary = IntField(min_val=0, nullable=True)


# ── 使用演示

emp = Employee(
    id=1001,
    name="张伟",
    age=32,
    department="技术部",
    salary=25000,
)
print(emp)
# Employee(created_at=None, updated_at=None, id=1001, name='张伟', age=32, department='技术部', salary=25000)

print(Employee._fields.keys())
# dict_keys(['created_at', 'updated_at', 'id', 'name', 'age', 'department', 'salary'])

# 验证错误
try:
    emp.salary = -1000
except ValueError as e:
    print(e)   # 值 -1000 小于最小值 0

这个案例中,MRO 确保了 Employee_fields 正确包含了来自 TimestampMixinUserBase 的所有字段,且 Employee 自身定义的字段优先于继承的字段(在字典更新顺序上体现)。


关键结论

理解属性查找顺序,有几条规律值得反复记忆:

查找优先级 :数据描述符 → 实例 __dict__ → 非数据描述符/类属性

@property 为何能阻止实例 __dict__ 覆盖property 是数据描述符(同时定义了 __get____set__),优先级高于实例字典。

函数为何可以被实例属性覆盖 :函数对象只是非数据描述符(只有 __get__),优先级低于实例字典。

MRO 的顺序决定了方法查找、super() 的目标、Mixin 的生效顺序。C3 算法在菱形继承中保证一致性,且在类定义阶段就完成验证。

super() 不是"父类"而是"MRO 中的下一个"------在多重继承场景中,这一区别直接影响所有 Mixin 能否正确协作。


小结

属性查找顺序和 MRO 是 Python 面向对象体系中连接多个机制的核心枢纽:

机制 与属性查找/MRO 的关系
@property 数据描述符,优先于实例 __dict__
方法绑定 函数作为非数据描述符,通过 __get__ 实现绑定
super() 在 MRO 当前类之后继续查找
多重继承 MRO 决定所有方法和属性的查找顺序
Mixin 模式 通过 MRO 将正交功能注入目标类
ORM 字段 数据描述符 + MRO 实现跨类字段继承

每一次 obj.attr 的访问背后,都是这套精确机制在高速运转。理解它不是为了应对面试,而是在碰到继承体系中属性"神秘消失"或方法"意外被覆盖"时,能够快速定位到正确的位置。


如果这篇文章对理解 Python 面向对象的底层机制有所帮助,欢迎点赞收藏。关注专栏,模块五的其余内容正在持续更新中。

有关描述符协议的内容可以参考描述符协议:@property 与 @classmethod 的实现原理,两篇文章结合阅读效果更佳。

相关推荐
不知名的老吴5 小时前
浅谈:树形动态规划中的换根技巧
算法·动态规划
一条大祥脚5 小时前
2021-2022 ICPC Southwestern Europe Regional Contest
算法·深度优先·图论
甄心爱学习5 小时前
【项目实训】法律文书智能摘要系统6
python·个人开发
运维行者_5 小时前
云计算连接性与互操作性
服务器·开发语言·网络·web安全·网络基础设施
郝学胜-神的一滴5 小时前
Qt 高级开发 010: 从跨界面传值到自定义信号
开发语言·c++·qt·程序人生·用户界面
社交怪人5 小时前
【浮点数相除的余】信息学奥赛一本通C语言解法(题号1029)
c语言·开发语言
努力弹琴的大风天5 小时前
如何用AI开发matlab/Simulink工具栏模块,实现相关的功能
开发语言·人工智能·matlab
罗湖老棍子5 小时前
The xor-longest Path(信息学奥赛一本通- P1478)
算法·字符串·字典树··lca最近公共祖先
小白学大数据5 小时前
Scrapling:极简高效的 Python 智能爬虫框架
开发语言·爬虫·python·数据分析