Python继承及方法解析顺序(MRO)详解 | 示例与super()函数使用

简介: Python继承及方法解析顺序(MRO)详解 | 示例与super()函数使用

继承

继承是面向对象编程中的一个重要概念。通过继承,我们可以让一个类获取到其他类中的属性和方法,避免编写重复性的代码,并且符合开闭原则(OCP)。继承是使一个类扩展的常用方式。

定义一个类 Animal

我们先定义一个类 Animal,这个类中有两个方法 run() 和 sleep(),表示动物会跑和睡觉。

class Animal:
    def run(self):
        print('动物会跑~~~')
    def sleep(self):
        print('动物睡觉~~~')

定义一个类 Dog

现在,我们想定义一个类 Dog,这个类除了能够跑和睡觉外,还能够汪汪叫。我们可以直接修改 Animal 类,在其中添加 bark() 方法,但这样会违反 OCP 原则。更好的方式是通过继承

class Dog(Animal):
    def bark(self):
        print('汪汪汪~~~') 
    def run(self):
        print('狗跑~~~~')  

在上面的代码中,我们使用 class Dog(Animal): 来指定 Dog 类继承自 Animal 类。这样,Dog 类就能够直接获取到 Animal 类的属性和方法。我们还可以重写 Animal 类中的方法,如在 Dog 类中重新定义了 run() 方法。

创建对象并调用方法

现在我们可以创建 Dog 类的对象 d = Dog(),并调用它的方法,比如 d.run()d.sleep()d.bark()

d = Dog()
d.run()  # 输出:狗跑~~~~
d.sleep()  # 输出:动物睡觉~~~
d.bark()  # 输出:汪汪汪~~~

类之间的关系

通过使用继承,我们可以建立类之间的层次关系。在创建类时,如果省略了父类,则默认父类为 objectobject 是所有类的父类,所以所有类都继承自 object

class Person(object):
    pass

我们可以使用 issubclass() 函数检查一个类是否是另一个类的子类,如 issubclass(Animal, Dog) 返回 False,而 issubclass(Animal, object) 返回 True

我们还可以使用 isinstance() 函数来检查一个对象是否是一个类的实例。如果这个类是这个对象的父类,也会返回 True。所有的对象都是 object 的实例。

print(isinstance(d, Dog))  # 输出:True
print(isinstance(d, Animal))  # 输出:True
print(isinstance(d, object))  # 输出:True
print(isinstance(print, object))  # 输出:True

继承使得类之间的关系更加清晰,并且方便代码的复用和扩展。

多重继承

除了单一继承外,Python 还支持多重继承,即一个子类可以从多个父类中继承属性和方法。这为我们提供了更大的灵活性,使得代码的组织和复用更加方便。

定义一个类 Hashiqi

假设我们还有一个类 Hashiqi,表示一种特殊的狗,它除了具备狗的基本行为外,还有自己特有的方法 fan_sha(),表示傻傻的哈士奇。

class Hashiqi(Dog):
    def fan_sha(self):
        print('我是一只傻傻的哈士奇')

在上面的代码中,我们定义了一个 Hashiqi 类,它继承自 Dog 类。因此,Hashiqi 类不仅能够拥有 Animal 类和 Dog 类中的属性和方法,还具备自己特有的 fan_sha() 方法。

创建对象并调用方法

现在我们可以创建 Hashiqi 类的对象 h = Hashiqi(),并调用它的方法,比如 h.run()h.sleep()h.bark()h.fan_sha()

h = Hashiqi()
h.run()  # 输出:狗跑~~~~
h.sleep()  # 输出:动物睡觉~~~
h.bark()  # 输出:汪汪汪~~~
h.fan_sha()  # 输出:我是一只傻傻的哈士奇

通过多重继承,我们可以实现更灵活和具有复杂关系的类结构。在创建子类时,只需指定多个父类,并且子类可以直接获取到所有父类中的属性和方法,从而减少了代码的冗余。

继承是面向对象编程的重要特性之一,它能够提高代码的可读性、可维护性和可扩展性,并符合开闭原则。在设计类的时候,我们应该充分考虑继承关系,遵循良好的代码组织和设计原则。

当一个类继承了多个父类时,可能会遇到命名冲突的问题。比如,如果两个父类都有相同名称的方法或属性,那么在子类中调用这个名称时会出现歧义。为了解决这个问题,Python 提供了方法解析顺序(Method Resolution Order,简称MRO)。

方法解析顺序(MRO)

在 Python 中,每个类都有一个方法解析顺序,即它继承的父类被搜索的顺序。可以通过 类名.__mro__ 或者 类名.mro() 来查看方法解析顺序。下面是一个例子:

class A:
    def method(self):
        print("A")
class B(A):
    def method(self):
        print("B")
class C(A):
    def method(self):
        print("C")
class D(B, C):
    pass
print(D.__mro__)
print(D.mro())

运行上面的代码,会输出以下结果:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

从输出结果可以看出,方法解析顺序是 D -> B -> C -> A -> object。也就是说,在 D 类的实例上调用 method() 方法时,会按照该顺序依次查找,并执行第一个匹配到的方法。

super() 函数

super() 函数是用于调用父类方法的一种方式。可以使用 super().方法名() 的形式来调用父类的方法,而不需要显式地指定父类的名称。比如:

class A:
    def method(self):
        print("A")
class B(A):
    def method(self):
        super().method()
        print("B")
b = B()
b.method()

输出结果为:

A
B

在上述代码中,B 类继承自 A 类,并在自己的 method() 方法中使用 super().method() 这一方式调用了父类 A 中的 method() 方法,并在其后打印了 “B”。

使用 super() 函数,可以保证在多重继承时按照方法解析顺序依次调用父类的方法,从而避免命名冲突和歧义。

当在多重继承中存在钻石继承(diamond inheritance)的情况时,为了避免方法重复调用和冗余代码,Python 使用 C3 线性化算法来确定方法解析顺序(Method Resolution Order,MRO)。

C3 线性化算法

C3 线性化算法通过合并多个父类的线性化顺序,生成一个满足以下条件的线性化列表:

  1. 子类永远在父类前面。
  2. 如果一个类在列表中的前面出现,那么它的所有父类也都在其前面。
  3. 如果多个父类都在一个类的后面,那么它们的顺序保持不变。

这样,在方法解析顺序中,每个类的方法只会被调用一次,避免了重复调用和冗余代码。

示例

以下是一个钻石继承的示例:

class A:
    def method(self):
        print("A")
class B(A):
    pass
class C(A):
    def method(self):
        print("C")
class D(B, C):
    pass
d = D()
d.method()

在上述代码中,类 D 继承了 B 和 C,而 B 和 C 都继承了 A。当我们创建 D 的实例并调用 method() 方法时,根据 C3 线性化算法得到的方法解析顺序是 D -> B -> C -> A,因此输出结果为 “C”。

super() 函数和钻石继承

在使用 super() 函数时,Python 会根据 MRO 确定的方法解析顺序来调用父类的方法。在钻石继承的情况下,super() 函数会按照 MRO 的顺序依次调用每个父类的方法,并保证每个方法只被调用一次。

class A:
    def method(self):
        print("A")
class B(A):
    def method(self):
        super().method()
        print("B")
class C(A):
    def method(self):
        super().method()
        print("C")
class D(B, C):
    pass
d = D()
d.method()

运行上述代码,输出结果为:

A
C
B

在该示例中,类 D 的方法解析顺序是 D -> B -> C -> A。当调用 d.method() 方法时,super().method() 会依次调用 B、C 和 A 类的 method() 方法。通过 MRO,每个类的方法只会被调用一次,避免了重复调用和冗余代码。

经典类和新式类

在 Python 2.x 版本中,存在经典类和新式类的概念。经典类是指没有显式继承自 object 的类,而新式类则是显式继承自 object 的类。

在经典类中,Python 使用深度优先搜索的方法来解析方法调用顺序。这种方法存在一些问题,比如方法重复调用、无法实现多重继承时的方法解析顺序等。

在新式类中,Python 使用 C3 线性化算法来解析方法的调用顺序。这种方法可以保证方法只被调用一次,并且解决了多重继承时方法解析顺序不确定的问题。

为了更好地兼容新旧版本的 Python,在 Python 2.3 版本中引入了一个特殊语法,即在定义类时使用 class ClassName(object): 的形式,从而将所有类都定义为新式类。

在 Python 3.x 版本中,则已经默认将所有类定义为新式类,无需显式继承自 object。

总结

Python 中的多重继承给程序员提供了更灵活的设计选择,但也带来了一些挑战。为了避免命名冲突和歧义,在多重继承中需要正确地设置方法解析顺序(MRO),从而保证方法调用的正确性和效率。

Python3.x 已经默认将所有类定义为新式类,并使用 C3 线性化算法来解决多重继承的问题,不再需要特别注意 MRO 的设置。

在 Python 中,钻石继承(diamond inheritance)是指一个子类同时继承自两个有共同父类的类,形成了一个菱形的继承结构。这种继承结构会引发一些问题,例如方法重复调用和冗余代码。

为了解决钻石继承带来的问题,Python 使用 C3 线性化算法来确定方法的解析顺序(Method Resolution Order,MRO)。具体步骤如下:

  1. 按照类的声明顺序,生成一个拓扑排序的列表(DAG)。
  2. 在拓扑排序的列表中,检查每个节点的父类列表,并将其父类所在的位置移动到它自身的前面。这样,可以保证子类在父类之前。
  3. 对于多个父类同时出现在同一个节点之后的情况,需要按照它们在基类列表中的顺序保持不变。

通过使用 C3 线性化算法,Python 可以避免方法重复调用和冗余代码的问题。此外,Python 会在类的定义过程中自动计算 MRO,并将其存储在特殊属性 __mro__ 中,供开发者查看。

需要注意的是,在实际使用中,当存在钻石继承的情况时,可以通过合理设计类的继承关系和使用 super() 函数来避免问题的出现。合理利用多态性、组合等技术也可以减轻继承带来的复杂性。

目录
相关文章
|
10月前
|
XML JSON 数据处理
超越JSON:Python结构化数据处理模块全解析
本文深入解析Python中12个核心数据处理模块,涵盖csv、pandas、pickle、shelve、struct、configparser、xml、numpy、array、sqlite3和msgpack,覆盖表格处理、序列化、配置管理、科学计算等六大场景,结合真实案例与决策树,助你高效应对各类数据挑战。(238字)
1306 0
|
10月前
|
数据采集 存储 JavaScript
解析Python爬虫中的Cookies和Session管理
Cookies与Session是Python爬虫中实现状态保持的核心。Cookies由服务器发送、客户端存储,用于标识用户;Session则通过唯一ID在服务端记录会话信息。二者协同实现登录模拟与数据持久化。
|
11月前
|
JSON 缓存 开发者
淘宝商品详情接口(item_get)企业级全解析:参数配置、签名机制与 Python 代码实战
本文详解淘宝开放平台taobao.item_get接口对接全流程,涵盖参数配置、MD5签名生成、Python企业级代码实现及高频问题排查,提供可落地的实战方案,助你高效稳定获取商品数据。
|
11月前
|
存储 大数据 Unix
Python生成器 vs 迭代器:从内存到代码的深度解析
在Python中,处理大数据或无限序列时,迭代器与生成器可避免内存溢出。迭代器通过`__iter__`和`__next__`手动实现,控制灵活;生成器用`yield`自动实现,代码简洁、内存高效。生成器适合大文件读取、惰性计算等场景,是性能优化的关键工具。
476 2
|
11月前
|
机器学习/深度学习 文字识别 Java
Python实现PDF图片OCR识别:从原理到实战的全流程解析
本文详解2025年Python实现扫描PDF文本提取的四大OCR方案(Tesseract、EasyOCR、PaddleOCR、OCRmyPDF),涵盖环境配置、图像预处理、核心识别与性能优化,结合财务票据、古籍数字化等实战场景,助力高效构建自动化文档处理系统。
2709 0
|
11月前
|
机器学习/深度学习 JSON Java
Java调用Python的5种实用方案:从简单到进阶的全场景解析
在机器学习与大数据融合背景下,Java与Python协同开发成为企业常见需求。本文通过真实案例解析5种主流调用方案,涵盖脚本调用到微服务架构,助力开发者根据业务场景选择最优方案,提升开发效率与系统性能。
2213 0
机器学习/深度学习 算法 自动驾驶
1541 0
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
621 4
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

推荐镜像

更多