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

文章目录

继承

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

定义一个类 Animal

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

python 复制代码
class Animal:
    def run(self):
        print('动物会跑~~~')

    def sleep(self):
        print('动物睡觉~~~')

定义一个类 Dog

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

python 复制代码
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()

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

类之间的关系

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

python 复制代码
class Person(object):
    pass

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

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

python 复制代码
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(),表示傻傻的哈士奇。

python 复制代码
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()

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

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

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

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

方法解析顺序(MRO)

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

python 复制代码
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().方法名() 的形式来调用父类的方法,而不需要显式地指定父类的名称。比如:

python 复制代码
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. 如果多个父类都在一个类的后面,那么它们的顺序保持不变。

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

示例

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

python 复制代码
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 的顺序依次调用每个父类的方法,并保证每个方法只被调用一次。

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

python精品专栏推荐


python基础知识(0基础入门)

【python基础知识】0.print()函数
【python基础知识】1.数据类型、数据应用、数据转换
【python基础知识】2.if条件判断与条件嵌套
【python基础知识】3.input()函数
【python基础知识】4.列表和字典
【python基础知识】5.for循环和while循环
【python基础知识】6.布尔值和四种语句(break、continue、pass、else)
【python基础知识】7.实操-用Python实现"文字PK"小游戏(一)
【python基础知识】7.实操-用Python实现"文字PK"小游戏(二)
【python基础知识】8.编程思维:如何解决问题-思维篇
【python基础知识】9.函数的定义和调用
【python基础知识】10.用函数编写程序 - 实操篇
【python基础知识】10.用Python实现石头剪刀布小游戏-函数实操篇
【python基础知识】11.如何debug -常见报错原因及排查思路 - 思维篇
【python基础知识】12.类与对象(一)
【python基础知识】12.类与对象(二)
【python基础知识】13.类与对象(三)
【python基础知识】13.类与对象(四)
【python基础知识】14.图书管理系统的搭建(类与对象实操)
【python基础知识】15.编码基础知识
【python基础知识】16.文件读写基础及操作
【python基础知识】16."古诗默写题"的python实现(文件读写和编码-实操篇)
【python基础知识】17.模块的概念以及如何引入
【python基础知识】18.实操-使用python自动群发邮件
【python基础知识】19.产品思维以及流程图的使用 - 思维篇
【python基础知识】20."午饭吃什么"的python实现(产品思维-实操篇)
【python基础知识】21.高效偷懒的正确打开方式-毕业篇
【python文件处理】CSV文件的读取、处理、写入
【python文件处理】Excel自动处理(使用 openpyxl)
【python文件处理】-excel格式处理


python爬虫知识

【python爬虫】1.爬虫基础知识
【python爬虫】2.网页基础知识
【python爬虫】3.爬虫初体验(BeautifulSoup解析)
【python爬虫】4.爬虫实操(菜品爬取)
【python爬虫】5.爬虫实操(歌词爬取)
【python爬虫】6.爬虫实操(带参数请求数据)
【python爬虫】7.爬到的数据存到哪里?
【python爬虫】8.温故而知新
【python爬虫】9.带着小饼干登录(cookies)
【python爬虫】10.指挥浏览器自动工作(selenium)
【python爬虫】11.让爬虫按时向你汇报
【python爬虫】12.建立你的爬虫大军
【python爬虫】13.吃什么不会胖(爬虫实操练习)
【python爬虫】14.Scrapy框架讲解
【python爬虫】15.Scrapy框架实战(热门职位爬取)
【python爬虫】16.爬虫知识点总结复习

相关推荐
汤米粥2 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾5 分钟前
EasyExcel使用
java·开发语言·excel
Leo.yuan6 分钟前
数据量大Excel卡顿严重?选对报表工具提高10倍效率
数据库·数据分析·数据可视化·powerbi
拾荒的小海螺11 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Runing_WoNiu15 分钟前
MySQL与Oracle对比及区别
数据库·mysql·oracle
秀儿还能再秀25 分钟前
机器学习——简单线性回归、逻辑回归
笔记·python·学习·机器学习
马剑威(威哥爱编程)36 分钟前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
天道有情战天下36 分钟前
mysql锁机制详解
数据库·mysql
看山还是山,看水还是。38 分钟前
Redis 配置
运维·数据库·redis·安全·缓存·测试覆盖率
谷新龙00141 分钟前
Redis运行时的10大重要指标
数据库·redis·缓存