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() 函数来避免问题的出现。合理利用多态性、组合等技术也可以减轻继承带来的复杂性。

相关文章
|
3月前
|
JSON API 数据格式
洋码头商品 API 示例指南(Python 实现)
洋码头是国内知名跨境电商平台,提供商品搜索、详情、分类等API接口。本文详解了使用Python调用这些API的流程与代码示例,涵盖签名生成、请求处理及常见问题解决方案,适用于构建选品工具、价格监控等跨境电商应用。
|
3月前
|
缓存 JSON API
VIN车辆识别码查询车五项 API 实践指南:让每一俩车有迹可循(Python代码示例)
VIN(车辆识别代码)是全球唯一的17位汽车标识码,可快速获取车架号、发动机号、品牌型号等核心信息。在二手车交易、保险理赔、维修保养等场景中,准确解析VIN有助于提升效率与风控能力。本文介绍VIN码结构、适用场景,并提供Python调用示例及优化建议,助力企业实现车辆信息自动化核验。
644 1
|
3月前
|
JSON API UED
运营商二要素验证 API:核验身份的一致性技术实践(Python示例)
随着线上业务快速发展,远程身份核验需求激增。运营商二要素验证API通过对接三大运营商实名数据,实现姓名、手机号、身份证号的一致性校验,具备权威性高、实时性强的优势,广泛应用于金融、电商、政务等领域。该接口支持高并发、低延迟调用,结合Python示例可快速集成,有效提升身份认证的安全性与效率。
420 0
|
3月前
|
JSON API 数据格式
Python采集京东商品评论API接口示例,json数据返回
下面是一个使用Python采集京东商品评论的完整示例,包括API请求、JSON数据解析
|
3月前
|
测试技术 API 开发者
淘宝关键词搜索商品列表API接入指南(含Python示例)
淘宝关键词搜索商品列表API是淘宝开放平台的核心接口,支持通过关键词检索商品,适用于比价、选品、市场分析等场景。接口提供丰富的筛选与排序功能,返回结构化数据,含商品ID、标题、价格、销量等信息。开发者可使用Python调用,需注意频率限制与错误处理,建议先在沙箱环境测试。
|
2月前
|
数据采集 索引 Python
Python Slice函数使用教程 - 详解与示例 | Python切片操作指南
Python中的`slice()`函数用于创建切片对象,以便对序列(如列表、字符串、元组)进行高效切片操作。它支持指定起始索引、结束索引和步长,提升代码可读性和灵活性。
|
3月前
|
JSON API 开发者
一号店商品 API 示例指南(Python 实现)
本教程介绍如何使用 Python 调用一号店商品 API,涵盖商品搜索、详情、分类等接口的调用方法。内容包括注册认证、签名生成、代码实现及常见问题解决方案,并提供完整示例代码,帮助开发者快速接入一号店开放平台,构建电商工具与数据分析应用。
|
7月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
711 29
|
7月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
212 4
|
7月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

推荐镜像

更多