12 Python MRO

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

Python MRO(方法解析顺序)

转载请标明出处(http://blog.csdn.net/lis_12/article/details/52859376).

MRO (Method Resolution Order)

Python在多重继承时,如果父类中存在同名函数会产生二义性,Python中处理这个问题方法就是MRO.

如果对MRO发展史没兴趣的话,直接看C3算法.

经典类,MRO=深度优先搜索

经典类是一种不能继承的类,如果经典类被作为父类,子类调用父类的构造函数则会出错.

经典类的MRO采用 “深度优先搜索“,子节点顺序从左到右;

经典类没有__mro__属性;

当出现菱形继承时,深度优先搜索就会出现问题.下面给出了正常继承模式和菱形继承模式下,采用DFS得出的MRO:

<画继承关系图时,要注意继承顺序,先继承的在最左侧,谨记这点,继承顺序不同,mro也是不一样的>

dfs

  1. 正常继承:

    如果两个互不相关的类多重继承,此时MRO正常,不会引起任何问题;

  2. 菱形继承:

    B和C有公共父类D,假设C重写了D中的fun()方法,按照MRO顺序先找到D中的fun()方法,此时C中重写的fun()方法将永远访问不到,导致了C只能继承不能重写D中的方法(即使C重写了fun()方法也不会访问到),这就是深度优先搜索的缺陷;

新式类,MRO = C3

为了使类和内置类型更加统一,解决经典类中多重继承只能继承不能重写的问题,引入了新式类.新式类的每个类都继承于一个基类(object),可以是自定义类或其他类,默认继承于object.子类可以调用父类的构造函数.

#经典类
class A():
    pass
#新式类
class A(object):
    pass

新式类采用C3算法求解mro,并且新式类是有__mro__属性的.

BFS,广度优先搜索

在介绍C3算法时,有必要了解BFS.如果新式类的MRO采用BFS(广度优先搜索,子节点顺序从左到右)

当出现正常继承时,广度优先搜索就会出现问题.下面给出了正常继承模式和菱形继承模式下,采用BFS得出的MRO:

<画继承关系图时,要注意继承顺序,先继承的在最左侧,谨记这点,继承顺序不同,mro也是不一样的>

bfs

  1. 正常继承:

    如果两个互不相关的类多重继承,则会发生问题.

    假设:D中有foo()方法,C也有foo()方法,则按照MRO顺序,应该是先在B中查找,再在C中查找.

    正常情况下,如果在B中未找到的话,应该在B的父类中查找,而不是在C中查找.

    这种先在B中查找然后再D中查找的顺序,称为单调性.(这就像继承遗产一样,如果继承人的儿子媳妇不在了,要先找继承人的孙子,孙女啊,而不是找什么侄子,外甥什么的…..)

  2. 菱形继承:

    解决了深度优先搜索出现的只能继承不能重写问题,但是违反了继承的单调性原则.

C3算法

下面开始正题,从Python2.3开始,新式类的MRO采用C3算法,解决了单调性和只能继承无法重写的问题.

MRO采用C3算法后,此时上述两种继承模式的MRO:

C3

代码实现:

#!/usr/bin/python
# -*- coding: utf-8 -*-
#正常继承.py
class D(object):
    def __init__(self):
        super(D,self).__init__()
        print 'D'

    def fun(self):
        print 'D fun()'

class E(object):
    def __init__(self):
        super(E,self).__init__()
        print 'E'

class C(E):
    def __init__(self):
        super(C,self).__init__()
        print 'C'
    def fun(self):
        print 'C fun()'

class B(D):
    def __init__(self):
        super(B,self).__init__()
        print 'B'

class A(B,C):
    def __init__(self):
        super(A,self).__init__()
        print 'A'

if __name__ == '__main__':#主程序
    a = A()  #E C D B A
    print A.__mro__#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <type 'object'>)  正确
    a.fun()  #D fun() 按照MRO搜索fun()方法,在D中找到

#菱形继承.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

class D(object):
    def __init__(self):
        super(D,self).__init__()
        print 'D'

    def fun(self):
        print 'D fun()'

class C(D):
    def __init__(self):
        super(C,self).__init__()
        print 'C'

    def fun(self):
        print 'C fun()'

class B(D):
    def __init__(self):
        super(B,self).__init__()
        print 'B'

class A(B,C):
    def __init__(self):
        super(A,self).__init__()
        print 'A'

if __name__ == '__main__':#主程序
    a = A()  #DCBA
    print A.__mro__ #(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>)   正确
    a.fun()  #C fun()  按照MRO搜索fun()方法,在C中找到

C3算法成功解决了单调性和只能继承无法重写的问题.下面来看下神奇的C3算法是如何计算mro的.

C3算法计算访问顺序列表

merge list公式法

C3算法的核心就是merge.

在merge列表中,如果第一个序列mro的第一个类出现在其它序列,并且也是第一个,或者某个类仅在一个序列中存在,其它序列不存在,那么这个类就会从这些序列中删除,并合到访问顺序列表中,如果在第一个序列中未找到满足上述条件的类,则在第二个序列开始查找,如果在第二个中也找不到符合条件的,则在第三个序列中查找……

谨记:mro(object) = [O]

如:class B(A1,A2,A3 …) 继承顺序也很重要,不要忽略了

此时B的mro序列 mro(B) = [B] + merge(mro(A1), mro(A2), mro(A3) …, [A1,A2,A3])

merge中的排列要严格遵守继承顺序.

以正常继承模式的几个类为例:(O为object类,新式类为object的子类)

正常继承

mro(D) = [D,O]

mro(E) = [E,O]

mro(B) = [B] + merge(mro(D) ,[D]) = [B] + merge([D,O] + [D]) = [B,D] +merge([O]) = [B,D,O]

同理:mro(C) = [C,E,O]

mro(A) = [A] + merge(mro(B),mro(C),[B,C])

​ = [A] + merge([B,D,O],[C,E,O],[B,C]) #B为第一个序列的第一个类,并且B在第三个序列中也是第一个类,合并

​ = [A,B] + merge([D,O],[C,E,O],[C]) #D仅在第一个序列中存在,合并

​ = [A,B,D] + merge([O],[C,E,O],[C]) #第一个序列中O不满足任何条件,在第二个序列中查找,C为第二个序

​ 的第一个类,并且也为第三个序列的第一个类,合并

​ = [A,B,D,C] + merge([O],[E,O]) #第一个序列中的O不满足任何条件,在第二个序列中查找,E仅在第二

​ 个序列中存在,合并

​ = [A,B,D,C,E] + merge([O],[O]) #O为第一个序列的第一个类,也是第二个序列的第一个类,合并

​ = [A,B,D,C,E,O]
result: [A,B,D,C,E,O] 正确

拓扑排序求解mro

解决单调性(BFS正常继承出现的问题):只要保证从根到叶,从左到右的访问顺序即可;

只能继承,不能重写(DFS菱形继承出现的问题):主要是因为先访问了子类的父类导致的.

如果熟悉图论的话,应该能想到拓扑排序能很好的解决上述问题.

拓扑排序:

对于一个有向无环图G进行拓扑排序,就是将G中所有的顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)属于E(G),则u在线性序列中出现在v之前.通常,这样的线性序列称之为满足拓扑次序的序列,简称拓扑排序.

因为拓扑排序肯定是由根节点到叶节点,所以只要满足从左到右的原则,得到的拓扑排序就是mro.

以正常继承模式和菱形继承模式的几个类为例,用拓扑排序求解mro:

正常继承

  1. 找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
  2. 再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
  3. 再找入度为0的点,有两个点D和C,取最左,so将D加入mro顺序列表中,剪掉与D相连的边;
  4. 再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
  5. 再找入度为0的点,只有E,so将E加入mro顺序列表中;

result: mro = [A,B,D,C,E] 正确

菱形继承:

这里写图片描述

  1. 找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
  2. 再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
  3. 再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
  4. 再找入度为0的点,只有D,so将D加入mro顺序列表中;

result: mro = [A,B,C,D] 正确

比较BFS,DFS,C3求解mro

菱形继承

算法正常继承 mro顺序菱形继承 mro顺序
DFSA,B,D,C,EA,B,D,C (只能继承,不能重写问题)
BFSA,B,C,D,E(不满足单调性原则)A,B,C,D
C3A,B,D,C,EA,B,C,D

实战计算mro

#!/usr/bin/python
# -*- coding: utf-8 -*-
class E(object):
    pass

class D(object):
    pass

class F(object):
    pass

class C(D,F):
    pass

class B(E,D):
    pass

class A(B,C):
    pass

if __name__ == '__main__':
    print A.__mro__#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type 'object'>)

上述代码的继承关系图,继承顺序为从左到右:

继承关系图

利用merge计算A的mro:(O代表object类)

mro(E) = [E,O]

mro(D) = [D,O]

mro(F) = [F,O]

mro(B) = [B] + merge(mro(E),mro(D),[E,D])

​ = [B] + merge([E,O],[D,O],[E,D]) E符合merge条件

​ = [B,E] + merge([O],[D,O],[D]) D符合merge条件

​ = [B,E,D] + merge([O],[O],[]) O符合merge条件

​ = [B,E,D,O]

mro(C) = [C] + merge(mro(D),mro(F),[D,F])

​ = [C] + merge([D,O],[F,O],[D,F]) D符合merge条件

​ = [C,D] + merge([O],[F,O],[F]) F符合merge条件

​ = [C,D,F] + merge([O],[O],[]) O符合merge条件

​ = [C,D,F,O]

mro(A) = [A] + merge(mro(B),mro(C),[B,C])

​ = [A] + merge([B,E,D,O] ,[C,D,F,O] ,[B,C]) B符合merge条件

​ = [A,B] + merge([E,D,O] ,[C,D,F,O] ,[C]) E符合merge条件

​ = [A,B,E] + merge([D,O] ,[C,D,F,O] ,[C]) C符合merge条件

​ = [A,B,E,C] + merge([D,O] ,[D,F,O] ,[]) D符合merge条件

​ = [A,B,E,C,D] + merge([O] ,[F,O] ,[]) F符合merge条件

​ = [A,B,E,C,D,F] + merge([O] ,[O] ,[]) O符合merge条件

​ = [A,B,E,C,D,F,O] 与程序结果一致

利用拓扑排序计算mro

根据继承关系图,(简单来说就是不停找入度为0的点,然后减去相连的边,不停重复,直至只剩下一个点)

  1. 找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
  2. 再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
  3. 再找入度为0的点,有两个点E和C,取最左,so将E加入mro顺序列表中,剪掉与E相连的边;
  4. 再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
  5. 再找入度为0的点,有两个点D和F,取最左,so将D加入mro顺序列表中,剪掉与D相连的边;
  6. 再找入度为0的点,只有F,so将F加入mro顺序列表中,剪掉与F相连的边;
  7. 只剩下object,将object加入mro顺序列表中;

此时得出mro = [A,B,E,C,D,F,O],与程序结果一致.

总结

mro的计算不必深究,了解就好,在实际应用时,直接使用class.__mro__查看方法解析顺序即可.另外在开发中要尽量避免多重继承,不要混用经典类和新式类……

参考网址

  1. (http://xymlife.com/2016/05/22/python_mro/)
  2. (https://segmentfault.com/a/1190000000749061)
  3. (http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html)
  4. (http://www.cnblogs.com/i2u9/archive/2013/03/19/pythonmroc3.html)​
Python 3.12 多重继承实战:3种 super() 调用方式与 MRO 顺序详解 本文深入解析Python 3.12中多重继承的实战技巧,重点介绍3种super()调用方式与MRO顺序的底层原理。通过电商支付网关的实例演示钻石继承问题的解决方案,对比无参数super()、显式指定super(,self)和直接调用父方法的优缺点,帮助开发者避免常见陷阱并优化代码结构。 阅读详情

相关推荐

Python MRO方法解析顺序

多重继承意味着单个子可以继承多个,并且子将被授权访问属性和函数,除非它们不是该特定的私有属性和函数。MRO 技术用于搜索正在执行的顺序。在这篇文章中,我们将学习Python MRO方法解析顺序)。

迹忆客 636

Python多继承机制与MRO深度解析

Python的面向对象编程中,多继承是一个强大的特性,它允许一个继承自多个父,从而集成多个父的属性和方法。然而,多继承也带来了一个复杂的问题:当子调用一个方法时,如果多个父中都存在这个方法Python应该如何决定调用哪个父方法MRO是一个静态确定的顺序,它在定义时就已经确定,并且在的生命周期中保持不变。在复杂的继承关系中,正确的MRO可以确保我们期望的方法被正确调用,避免潜在的问题和混淆。在实际编程中,如果遇到多继承相关的问题,我们可以通过查看MRO来调试和解决问题。

铭渊老黄 1337

Python super()与MRO:多重继承中协作初始化的原理与实战

Python面向对象编程中,继承的本质是方法解析链(MRO)驱动的动态委托,而非静态属性复制。super()并非简单调用父,而是基于C3线性化算法,在运行时按MRO顺序向下一个发起协作式初始化请求。这一机制对解决钻石继承中的重复初始化、参数传递断裂和属性缺失等关键问题具有不可替代的技术价值。广泛应用于电商数据清洗、金融风控计算、NLP文本分析流水线等需功能组合与可扩展设计的工程场景。本文深入剖析super()的代理本质、MRO动态绑定特性及__init__协作范式,直击AttributeError与

weixin_30719711的博客 361

python中的MRO

一:什么是MRO 所谓的MRO,就是方法解析顺序(Method Resolution Order)。在调用方法时,会对当前以及所有的基进行一个搜索,确定要调用的方法具体在哪。不管用哪种方式去确定MRO列表,必须满足 本地优先级和单调性。 本地优先级:指声明时父顺序,比如C(A,B),如果访问C对象属性时,应该根据声明顺序,优先查找A,然后再查找B 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子里,也必须满足这个顺序 二:常见的mro算法 1:mro=深度优先遍历的(

zhangdell的专栏 8565

Python 中的方法解析顺序MRO

Python 中,MRO(Method Resolution Order,方法解析顺序)是指继承体系中,Python 如何确定在调用方法时的解析顺序MRO 决定了在多继承环境下,Python 如何寻找方法或属性,即它会根据一定规则从父中查找方法或属性,避免重复继承带来的歧义。

NLP工程化 3370

Python3的多继承与MRO顺序

python是一种面向对象的编程语言,其中重要编程方式之一是。通过继承,子便可以扩展父的功能,实现多态。继承有两种形式,一种是单继承,即一个只继承一个父;另一种是当然,子所继承的所有父同样也能有自己的父,同样也可以实现多继承!fill:#333;color:#333;color:#333;fill:none;

qq_38380338的博客 1826

Python 3.12 多重继承实战:super() 调用顺序MRO3 种验证方法

本文深入解析Python 3.12中多重继承下`super()`的调用顺序方法解析顺序MRO),提供三种验证MRO的实用方法,包括使用`__mro__`属性、`inspect`模块和手动推算C3算法。通过菱形继承实例和最佳实践,帮助开发者掌握Python面向对象编程中的复杂继承关系。

liudaoru - 悟 665

Python 3.12 继承实战:super() 解决多继承 MRO3 个典型场景

本文深入探讨Python 3.12继承的实战应用,重点解析super()在多继承MRO中的三个典型场景。通过菱形继承、Mixin协作和动态MRO调整的实例,帮助开发者掌握面向对象编程的核心技巧,解决父方法覆盖和属性初始化等常见问题。

weixin_30457465的博客 441

Python 3.12 Special Attribute - 10 - __mro__

Python __mro__ 属性是Python的重要特殊属性,表示方法解析顺序(MRO)。它是一个元组,按C3线性化算法列出及其所有祖先,决定了多重继承时的方法查找顺序

山低水浅有陋室,檐下听雨敲键盘 517

Python 3.12 继承 3 大陷阱:从 `__init__` 重写到 `super()` 参数传递

本文深入解析Python 3.12继承的三大常见陷阱,包括子重写`__init__`导致父初始化失效、多重继承中`super()`的误用以及方法调用时的参数传递错误。通过具体代码示例和解决方案对比,帮助开发者避免这些常见错误,提升代码质量与可维护性。

cuiyingchan0663的博客 493

Python3中的加载方式

本文Python版本 python --version Python 3.8.12 同一脚本中的 python中的__init__方法 class Leave: def __init__(self): print('Leave in') su = super() su.__init__() print('Leave out') class Left(Leave): def __init__(self):

星云 1184

Python 3.12 变量与实例变量实战:3个常见陷阱与内存优化方案

本文深入探讨Python 3.12变量与实例变量的核心区别,揭示3个常见陷阱并提供解决方案,包括意外共享可变变量、实例属性遮蔽变量等问题。同时分享内存优化实战方案,如使用`__slots__`、共享不可变数据和描述符控制属性存储,帮助开发者编写更高效、可靠的面向对象编程代码。

weixin_34161064的博客 465

Python 3.12 MagicMethods - 06 - __str__

本文深入解析Python中的__str__魔术方法,它定义了对象的用户友好字符串表示形式。与__repr__不同,__str__旨在为最终用户提供简洁易读的输出。

山低水浅有陋室,檐下听雨敲键盘 511

Python 3.12 MagicMethods - 104 - __subclasscheck__

Python 3.12 魔术方法 __subclasscheck__ 是 Python中的魔术方法,用于自定义 issubclass() 的行为。它允许开发者控制之间的继承关系判断,支持虚拟子、动态继承等高级特性。

山低水浅有陋室,檐下听雨敲键盘 203

Python 3.12 MagicMethods - 05 - __repr__

Python 3.12中的__repr__是Python中定义对象官方字符串表示的关键方法,主要用于开发和调试场景。本文全面剖析了该方法,包括其定义、用途、设计原则、底层实现机制及具体示例。

山低水浅有陋室,檐下听雨敲键盘 504

Python 继承,__bases__, __mro__, super

Python是面向对象的编程语言,也支持继承。 >>> class Base: ... pass ... >>> class Derived(Base): ... pass    这样就定义了两个,Derived继承了Base。issubclass(a,b)可以测试继承关系: >>> issubclass(Derived, Base) True    在

iCode0410的专栏 8997

python多继承下的查找顺序-MRO原则演变与C3算法

python历史版本中的演变史 python2.2之前: MRO原则:   只有经典,遵循深度优先(从左到右)原则,   存在的问题:在有重叠的多继承中,违背重写可用原则   解决办法是再设计的时候不要设计这样有问题的 python2.2版本: MRO原则:   经典:遵循深度优先(从左到右)原则   引入新式:在深度优先(从左到右)的算法基础之上, ...

weixin_30561177的博客 203

PythonMRO三定律”——关于Python中多继承C3-MRO算法的剖析

本文将带你由浅至深,逐步理解Python中的多继承C3-MRO算法。 本文从基础的MRO认识出发,简单了解C3-MRO的历史后展开C3-MRO算法的详细内容。接着通过“MRO三定律”的特殊角度深度解析C3-MRO算法的核心思想以及算法原理。

W-H2099的博客 1179
上一篇: 11 Python pyinstaller--将Python程序打包成单独的exe可执行文件
下一篇: 13 Python super
忧桑的小兔子
博客等级 码龄10年 105粉丝 199原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值