当前位置: 首页 > news >正文

重学python之mro

文章目录

    • 1. 背景
    • 2. MRO是什么?
    • 3. 继承规则
      • 3.1 MRO 决定初始化顺序
    • 4. 如果父类有不同参数的__init__?
    • 5. 如何让多继承的子类调用所有父类的__init__?
    • 6. 附录
      • a. C3 线性化算法

1. 背景

先看这么一个问题?

python的继承是可以继承父类的属性和函数的,而python支持多继承,假设类C继承于类A和类B,那么,类C是否同时包含了A和B的初始化函数__init__?, 类C是否既可以按照类A的构造方法初始化,也可以按照类B的构造函数初始化?

答案是只会继承其中一个父类的 __init__ 方法。具体继承哪个父类的 init,取决于 Python 的方法解析顺序(MRO,Method Resolution Order)。


2. MRO是什么?

MRO,Method Resolution Order,中文意思为方法解析顺序,在python中, __mro__是 Python 中用于表示方法解析顺序(Method Resolution Order) 的特殊属性。它是一个元组,存储了类在多继承时查找方法(包括属性)的顺序。

当一个类继承自多个父类时,Python 需要确定:当调用一个方法或访问一个属性时,应该按照什么顺序去父类中查找。mro 正是这个查找顺序的具体体现。

适用场景: 主要用于多继承,解决 “菱形继承”(多个父类最终继承自同一个基类)等复杂继承关系中的方法查找歧义。
查找规则:

  • python 会按照 mro 中类的顺序依次查找方法 / 属性,找到第一个匹配项后立即返回。
  • 对于单继承,mro 顺序就是 “子类 → 父类 → 祖父类 → … → object”。
  • 对于多继承,mro 遵循 “C3 线性化算法”,确保查找顺序的一致性和合理性。

示例

class A:passclass B(A):passclass C(A):passclass D(B, C):  # 多继承 B 和 Cpass
</pre>
# 查看 D 类的方法解析顺序
print(D.__mro__)
# 输出:(D, B, C, A, object)

可以通过 类名.mro 或 inspect.getmro(类名) 查看任意类的方法解析顺序。


3. 继承规则

  • 单继承遵循 “深度优先”
    子类 → 直接父类 → 祖父类 → … → 顶层基类(如 object),形成线性查找链。
  • 多继承遵循 C3 线性化算法
    核心是生成一个无歧义的方法查找顺序,确保:
    • 保持子类声明的父类顺序(如 class C(A, B) 中 A 优先于 B)
    • 父类的 MRO 顺序在子类中保持单调性(不颠倒祖先类顺序)
    • 每个类在 MRO 中仅出现一次
  • __mro__属性存储实际顺序
    通过 类名__mro__可查看具体顺序,查找方法 / 属性时严格按此顺序执行,找到第一个匹配项即停止。
  • super() 函数依赖 MRO
    super() 并非简单指向 “父类”,而是根据当前类的 MRO 顺序,返回下一个类的实例,用于调用继承链中的方法。
  • 非法继承关系会直接报错
    若 C3 算法无法生成合法的 MRO(如出现循环依赖或顺序冲突),Python 会抛出 TypeError 提示无法创建一致的方法解析顺序。

3.1 MRO 决定初始化顺序

  • 多继承时,Python 会按照 MRO 规定的顺序查找父类方法。对于 init 来说,只会执行 MRO 中第一个出现的、有 init 方法的父类的构造函数。

示例

class A:def __init__(self):print("A 的 __init__ 被调用")class B:def __init__(self):print("B 的 __init__ 被调用")# 多继承:C 继承 A 和 B,自身无 __init__
class C(A, B):pass  # 无自定义 __init__# 创建 C 的实例
c = C()  # 输出:"A 的 __init__ 被调用"

原因:

  • C 的 MRO 顺序是 [C, A, B, object](可通过 C.mro 查看)。
  • 解释器会先在 A 中找到 init 并执行,不会再去调用 B 的 init

4. 如果父类有不同参数的__init__?

若父类 A 和 B 的 init 有不同参数,直接实例化 C 可能报错:

class A:def __init__(self, x):print(f"A 的 __init__,x={x}")class B:def __init__(self, y):print(f"B 的 __init__,y={y}")class C(A, B):pass# 报错:A 的 __init__ 需要参数 x,但未提供
c = C()  # TypeError: A.__init__() missing 1 required positional argument: 'x'# 只能按 MRO 中第一个 __init__(A)的参数传参
c = C(x=1)  # 输出:"A 的 __init__,x=1"(B 的 __init__ 仍不会被调用)

5. 如何让多继承的子类调用所有父类的__init__?

需在子类 C 中显式定义 init,并通过 super() 或直接调用父类 init 来触发:

class C(A, B):def __init__(self, x, y):# 调用 A 的 __init__A.__init__(self, x)# 调用 B 的 __init__B.__init__(self, y)c = C(x=1, y=2)
# 输出:
# A 的 __init__,x=1
# B 的 __init__,y=2

6. 附录

a. C3 线性化算法

C3 算法的核心是为每个类生成一个线性化列表(linearization),这个列表是类本身、父类的线性化列表及父类声明顺序的 “合并结果”,需

满足以下原则

  • 保持父类顺序:子类声明的父类顺序(如 class D(B, C) 中 B 在 C 前)必须在结果中保持。
  • 单调性:若类 X 是类 Y 的祖先,则 X 在 Y 的线性化列表中出现的位置,必须在所有 Y 的子类的线性化列表中 X 出现的位置之前。
  • 唯一性:每个类在线性化列表中仅出现一次。

C3 算法的计算步骤
为类 C 计算线性化列表(记为 L©)的步骤如下:

  1. 初始化:L© 以 C 本身为起点,即 L© = [C] + merge(L(B1), L(B2), …, L(Bn), [B1, B2, …, Bn]),其中 B1, B2, …, Bn 是 C 直接继承的父类(按声明顺序)。
  2. 合并操作(merge):合并多个列表(父类的线性化列表 + 父类声明顺序列表),规则是:
    • 从第一个列表的头部取一个类 X;
    • 检查 X 是否在其他所有列表的尾部(即 X 不在其他列表的非尾部位置);
    • 若满足,将 X 加入结果,并从所有列表中移除 X;
    • 若不满足,取下一个列表的头部重试;
    • 重复直到所有列表为空(若无法合并则报错,说明继承关系不合理)。

示例

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass  # D 继承 B 和 C,B 和 C 都继承 A

计算各 class 的线性化列表:

  1. L(A):A 没有父类(除 object),所以 L(A) = [A, object]。
  2. L(B):B 继承 A,因此:
L(B) = [B] + merge(L(A), [A])L(A) = [A, object],[A] 是父类声明列表merge([A, object], [A]) → 取第一个列表的头部 A,检查是否在其他列表([A])的尾部(是),加入结果并移除 A → 剩余 [object] 和 []继续合并 [object] 和 [] → 加入 object → 最终 L(B) = [B, A, object]
  1. L(C):类似 B,L(C) = [C, A, object]。
  2. L(D):D 继承 B 和 C,因此:
L(D) = [D] + merge(L(B), L(C), [B, C])L(B) = [B, A, object],L(C) = [C, A, object],[B, C] 是父类声明列表第一步:检查第一个列表(L(B))的头部 B,是否在其他列表(L(C) = [C, A, object]、[B, C])的尾部?L(C) 中 B 未出现(满足);[B, C] 中 B 是头部(非尾部,不满足)→ 不能选 B。第二步:取下一个列表(L(C))的头部 C,检查是否在其他列表(L(B) = [B, A, object]、[B, C])的尾部?L(B) 中 C 未出现(满足);[B, C] 中 C 是尾部(满足)→ 选 C,加入结果并移除所有列表中的 C。剩余列表:L(B) = [B, A, object],L(C) = [A, object],[B]第三步:检查第一个列表头部 B,是否在其他列表(L(C) = [A, object]、[B])的尾部?L(C) 中 B 未出现(满足);[B] 中 B 是尾部(满足)→ 选 B,加入结果并移除 B。剩余列表:L(B) = [A, object],L(C) = [A, object],[]第四步:检查第一个列表头部 A,是否在其他列表(L(C) = [A, object])的尾部?是 → 选 A,移除后剩余 [object] 和 [object]。第五步:加入 object → 最终 L(D) = [D, C, B, A, object]

因此,D.mro 的结果为 (D, C, B, A, object),这就是 C3 算法计算的方法查找顺序。

http://www.dtcms.com/a/348294.html

相关文章:

  • 【科研绘图系列】R语言浮游植物初级生产力与光照强度的关系
  • 28.原型
  • 详解triton.jit及PTX
  • 目标检测数据集 第006期-基于yolo标注格式的汽车事故检测数据集(含免费分享)
  • vue 自定义文件选择器组件- 原生 input实现
  • 一文学习和掌握网关SpringCloudGateway
  • Java基础知识(五)
  • 南科大C++ 第二章知识储备
  • 电脑深度清理软件,免费磁盘优化工具
  • Shell脚本-如何生成随机数
  • 设置接收超时(SO_RCVTIMEO)
  • 8月精选!Windows 11 25H2 【版本号:26200.5733】
  • 牛市阶段投资指南
  • ffmpeg强大的滤镜功能
  • SingleFile网页保存插件本地安装(QQ浏览器)
  • 【图像处理基石】如何把非笑脸转为笑脸?
  • ffmpeg 问答系列-> mux 部分
  • 启动Flink SQL Client并连接到YARN集群会话
  • Node.js自研ORM框架深度解析与实践
  • 柱状图中最大的矩形+单调栈
  • STM32 入门实录:macOS 下从 0 到点亮 LED
  • Java全栈开发面试实录:从基础到实战的深度探讨
  • 微服务-19.什么是网关
  • 【论文阅读】AI 赋能基于模型的系统工程研究现状与展望
  • Redis--day12--黑马点评--附近商铺用户签到UV统计
  • Excel 表格 - 合并单元格、清除单元格格式
  • 包裹堆叠场景漏检率↓79%!陌讯多目标追踪算法在智慧物流的实践优化
  • EXCEL实现复制后倒序粘贴
  • 暗影哨兵:安全运维的隐秘防线
  • 深度学习部署实战 Ubuntu24.04单机多卡部署ERNIE-4.5-VL-28B-A3B-Paddle文心多模态大模型(详细教程)