文章目录
-
- 一个让人困惑的现象
- [第一部分:`object.getattribute` 的完整执行路径](#第一部分:
object.__getattribute__的完整执行路径) -
- [关键区分:数据描述符 vs 非数据描述符](#关键区分:数据描述符 vs 非数据描述符)
- [第二部分:MRO 的本质------C3 线性化算法](#第二部分:MRO 的本质——C3 线性化算法)
-
- 为什么不能用深度优先
- [当 C3 失败时](#当 C3 失败时)
- [第三部分:`super()` 的真实工作方式](#第三部分:
super()的真实工作方式) -
- [`super()` 不是"调用父类"](#
super()不是"调用父类") - [`super()` 的参数形式](#
super()的参数形式) - [协作继承(Cooperative Multiple Inheritance)](#协作继承(Cooperative Multiple Inheritance))
- [`super()` 不是"调用父类"](#
- [第四部分:`getattr` vs `getattribute` 的分工](#第四部分:
__getattr__vs__getattribute__的分工) - [第五部分:实例 `dict` 的内部结构与生命周期](#第五部分:实例
__dict__的内部结构与生命周期) -
- [`dict` 是什么](#
__dict__是什么) - [实例 `dict` vs 类 `dict`:写操作的差异](#实例
__dict__vs 类__dict__:写操作的差异)
- [`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
是数据描述符?
同时有 get 和 set/delete
调用 descriptor.get(obj, type(obj))
✓ 优先级最高,直接返回
记录为候选,继续向下
实例 obj.dict
中有 attr?
返回 obj.dict['attr']
✓ 实例属性优先于非数据描述符
候选是否有 get?
即非数据描述符?
调用 candidate.get(obj, type(obj))
典型:方法绑定
直接返回类属性值
返回结果
抛出 AttributeError
关键区分:数据描述符 vs 非数据描述符
| 类型 | 条件 | 优先级 | 典型例子 |
|---|---|---|---|
| 数据描述符 | 有 __get__ + 有 __set__ 或 __delete__ |
高于实例 __dict__ |
property、classmethod(内部)、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 线性化解决了这个问题,保证了三个约束:
- 子类优先:子类总是在父类前面
- 本地声明优先 :
class D(B, C)中 B 在 C 前,这个顺序被保留 - 单调性:不破坏父类自身的 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._cache 和 self._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 正确包含了来自 TimestampMixin 和 UserBase 的所有字段,且 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 的实现原理,两篇文章结合阅读效果更佳。