码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • Python继承及方法解析顺序(MRO)详解 | 示例与super()函数使用


    文章目录

    • 继承
      • 定义一个类 Animal
      • 定义一个类 Dog
      • 创建对象并调用方法
      • 类之间的关系
      • 多重继承
      • 定义一个类 Hashiqi
      • 创建对象并调用方法
      • 方法解析顺序(MRO)
      • super() 函数
      • C3 线性化算法
      • 示例
      • super() 函数和钻石继承
      • 经典类和新式类
      • 总结
    • python精品专栏推荐
      • python基础知识(0基础入门)
      • python爬虫知识

    继承

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

    定义一个类 Animal

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

    class Animal:
        def run(self):
            print('动物会跑~~~')
    
        def sleep(self):
            print('动物睡觉~~~')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    定义一个类 Dog

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

    class Dog(Animal):
        def bark(self):
            print('汪汪汪~~~') 
    
        def run(self):
            print('狗跑~~~~')   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在上面的代码中,我们使用 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()  # 输出:汪汪汪~~~
    
    • 1
    • 2
    • 3
    • 4

    类之间的关系

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

    class Person(object):
        pass
    
    • 1
    • 2

    我们可以使用 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
    
    • 1
    • 2
    • 3
    • 4

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

    多重继承

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

    定义一个类 Hashiqi

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

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

    在上面的代码中,我们定义了一个 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()  # 输出:我是一只傻傻的哈士奇
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    当一个类继承了多个父类时,可能会遇到命名冲突的问题。比如,如果两个父类都有相同名称的方法或属性,那么在子类中调用这个名称时会出现歧义。为了解决这个问题,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())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    (, , , , )
    [, , , , ]
    
    • 1
    • 2

    从输出结果可以看出,方法解析顺序是 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出结果为:

    A
    B
    
    • 1
    • 2

    在上述代码中,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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在上述代码中,类 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    A
    C
    B
    
    • 1
    • 2
    • 3

    在该示例中,类 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.爬虫知识点总结复习

  • 相关阅读:
    SVN常用操作
    【2023年11月第四版教材】第17章《干系人管理》(第一部分)
    数字图像处理实验记录二(直方图和直方图均衡化)
    【前端实例代码】Html5+css3+JavaScript实现新拟态新拟物风格(Neumorphism)气泡图标泡泡网页效果!手把手教学!新手必会!超简单 ~
    ES6-04-模块化的暴露:export关键字
    go语言添加代理
    【21天python打卡】第6天 面向对象编程(1)
    ------构造类型数据—结构体---- + ----函数-----
    阻抗&导纳控制理解
    贵阳RapidSSL的ssl证书适合个人网站吗
  • 原文地址:https://blog.csdn.net/qq_41308872/article/details/132852073
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号