Python - 面向对象编程 - MRO 方法搜索顺序(下)

简介: Python - 面向对象编程 - MRO 方法搜索顺序(下)

分析不同类的 MRO

  • A: A->X->Y->object
  • B: B->Y->X->object
  • C: C->A->B->X->Y->object

很明显,B、C 中间的 X、Y 顺序是相反的,就是说 B 被继承时,它的搜索顺序会被改变

在 python2 中运行这段代码的报错

image.png

在 python3 中运行这段代码的报错

image.png


C3 MRO 算法


  • 为解决前面两个算法的问题,Python 2.3 采用了 C3 方法来确定方法搜索顺序
  • 多数情况下,如果别人提到 Python 中的 MRO,指的都是 C3 算法

 

将上面第一个栗子的代码放到 python3 中运行

class A:
    def test(self):
        print("CommonA")
class B(A):
    pass
class C(A):
    def test(self):
        print("CommonC")
class D(B, C):
    pass
D().test()
# 输出结果
CommonC


简单了解下 C3 算法

以上面代码为栗子,C3 会把各个类的 MRO 等价为以下等式

  • A:L[A] = merge(A , object)
  • B:L[B] = B + merge(L[A] , A)
  • C:L[C] = C + merge(L[A] , A)
  • D:L[D] = D + merge(L[B] , L[C] , B , C)

 

了解一下:头、尾

以 A 类为栗,merge() 包含的 A 成为 L[A] 的头,剩余元素(这里只有 object)称为尾

 

merge 的运算方式

  1. 将 merge 第一个列表的头元素(如 L[A] 的头),记作 H
  2. 如果 H 出现在 merge 其他列表的头部,则将其输出,并将其从所有列表中删除
  3. 如果 H 只出现一次,那么也将其输出,并将其从所有列表中删除
  4. 如果 H 出现在 merge 其他列表的非头部,则取下一个列表的头元素记作 H,然后回到步骤二
  5. 最后回到步骤一,重复以上步骤

重复以上步骤直到列表为空,则算法结束;如果不能再找出可以输出的元素,则抛出异常

 

简单类 MRO 的计算栗子


class B(object): pass
print(B.__mro__)
(<class '__main__.B'>, <class 'object'>)


MRO 计算方式

L[B] = L[B(object)]
     = B + merge(L[object])
     = B + L[object]
     = B object


单继承 MRO 的计算栗子


# 计算 MRO
class B(object): pass
class C(B): pass
print(C.__mro__)
(<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)


MRO 计算方式

L[C] = C + merge(L[B])

    = C + L[B]

    = C B object

 

多继承 MRO 的计算栗子


O = object
class F(O): pass
class E(O): pass
class D(O): pass
class C(D, F): pass
class B(D, E): pass
class A(B, C): pass
print(C.__mro__)
print(B.__mro__)
print(A.__mro__)
# 输出结果
(<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
(<class '__main__.B'>, <class '__main__.D'>, <class '__main__.E'>, <class 'object'>)
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>)


O 类、object 类 MRO 计算

L[O] = O = object

 

D、E、F 类 MRO 计算

L[D] = D + merge(L[O])

       = D O

 

C 类 MRO 计算

L[C] = L[C(D, F)]
     = C + merge(L[D], L[F], DF)
     # 从前面可知 L[D] 和 L[F] 的结果
     = C +  merge(DO, FO, DF)
     # 因为 D 是顺序第一个并且在几个包含 D 的 list 中是 head,
     # 所以这一次取 D 同时从列表中删除 D
     = C + D + merge(O, FO, F)
     # 因为 O 虽然是顺序第一个但在其他 list (FO)中是在尾部, 跳过
     # 改为检查第二个list FO
     # F 是第二个 list 和其他 list 的 head
     # 取 F 同时从列表中删除 F
     = C + D + F + merge(O)
     = C D F O


B 类 MRO 计算

L[B] = L[B(D, E)]
     = B + merge(L[D], L[E], DE)
     = B + merge(DO, EO, DE)
     = B + D + merge(O, EO, E)
     = B + D + E + merge(O)
     = B D E O


A 类 MRO 计算

L[A] = L[A(B,C)]
        = A + merge(L[B], L[C], BC)
        = A + merge( BDEO, CDFO, BC )
        = A + B + merge( DEO, CDFO, C )
        # D 在其他列表 CDFO 不是 head,所以跳过到下一个列表的 头元素 C
        = A + B + C + merge( DEO, DFO )
        = A + B + C + D + merge( EO, FO )
        = A + B + C + D + E + merge( O, FO )
        = A + B + C + D + E + F + merge( O )
        = A B C D E F O


多继承 MRO 的计算栗子二


O = object
class F(O): pass
class E(O): pass
class D(O): pass
class C(D, F): pass
class B(E, D): pass
class A(B, C): pass
print(C.__mro__)
print(B.__mro__)
print(A.__mro__)
# 输出结果
(<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
(<class '__main__.B'>, <class '__main__.E'>, <class '__main__.D'>, <class 'object'>)
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)


O 类、object 类 MRO 计算

L[O] = O = object

 

D、E、F 类 MRO 计算

L[D] = D + merge(L[O])

       = D O

 

C 类 MRO 计算

L[C] = L[C(D, F)]

       = C + merge(L[D], L[F], DF)

       = C + merge(DO, FO, DF)

       = C + D + merge(O, FO, F)

       = C + D + F + merge(O)

       = C D F O

 

B 类 MRO 计算

L[B] = L[B(E, D)]

      = B + merge(L[E], L[D], ED)

      = B + merge(EO, DO, ED)

      = B + E + merge(O, DO, D)

      = B + E + D + merge(O)

      = B E D O

 

A 类 MRO 计算

L[A]  = L[A(B, C)]
        = A + merge(L[B], L[C], BC)
        = A + merge(BEDO, CDFO, BC)
        = A + B + merge(EDO, CDFO, C)
        = A + B + E + merge(DO,CDFO, C)
        = A + B + E + C + merge(O,DFO)
        = A + B + E + C + D + merge(O, FO)
        = A + B + E + C + D + F + merge(O)
        = A B E C D F O
相关文章
|
4天前
|
Python
【Python进阶(五)】——模块搜索及工作目录
【Python进阶(五)】——模块搜索及工作目录
|
4天前
|
C++ 开发者 Python
实现Python日志点击跳转到代码位置的方法
本文介绍了如何在Python日志中实现点击跳转到代码位置的功能,以提升调试效率。通过结合`logging`模块的`findCaller()`方法记录代码位置信息,并使用支持点击跳转的日志查看工具(如VS Code、PyCharm),开发者可以从日志直接点击链接定位到出错代码,加快问题排查。
13 2
|
4天前
|
索引 Python
Python 中寻找列表最大值位置的方法
本文介绍了Python中找列表最大值及其位置的三种方法:1) 使用内置`max()`和`index()`函数;2) 通过循环遍历;3) 利用`enumerate()`函数和生成器表达式。每种方法均附有示例代码,其中`enumerate()`方法在保证效率的同时代码更简洁。
25 2
|
4天前
|
JSON 数据处理 数据格式
Python中批量提取[]括号内第一个元素的四种方法
Python中批量提取[]括号内第一个元素的四种方法
23 1
|
4天前
|
SQL 关系型数据库 数据库连接
使用 Python 访问数据库的基本方法
【5月更文挑战第12天】在Python中操作数据库涉及安装数据库驱动(如mysql-connector-python, psycopg2, pymongo)、连接数据库、执行查询/更新、处理结果集及关闭连接。使用ORM(如SQLAlchemy)可简化操作。通过上下文管理器(with语句)能更好地管理资源和错误。注意根据实际需求处理事务、错误和安全性,例如使用SSL连接。
23 2
|
4天前
|
Python
【Python进阶(三)】——面向对象编程
【Python进阶(三)】——面向对象编程
|
4天前
|
测试技术 开发者 Python
Python检查函数和方法的输入/输出
【5月更文挑战第5天】Python检查函数和方法的输入/输出
13 1
|
4天前
|
Python
【Python进阶(二)】——程序调试方法
【Python进阶(二)】——程序调试方法
|
4天前
|
存储 Linux Shell
python移除/删除非空文件夹/目录的最有效方法是什么?
python移除/删除非空文件夹/目录的最有效方法是什么?
11 0
|
4天前
|
Python
【Python 基础】Python中的实例方法、静态方法和类方法有什么区别?
【5月更文挑战第6天】【Python 基础】Python中的实例方法、静态方法和类方法有什么区别?