python中super()函数的理解与基本使用


Posted in Python onAugust 30, 2021

前言

Python是一门面向对象的语言,定义类时经常要用到继承,在类的继承中,子类继承父类中已经封装好的方法,不需要再次编写,如果子类如果重新定义了父类的某一方法,那么该方法就会覆盖父类的同名方法,但是有时我们希望子类保持父类方法的基础上进行扩展,而不是直接覆盖,就需要先调用父类的方法,然后再进行功能的扩展,这时就可以通过super来实现对父类方法的调用。

super的用法

看下面一个例子:

class A:
    def func(self):
        print("A的func执行")


class B(A):

    def func(self):
        super().func()
        print("B扩展的func执行")


b = B()
b.func()
# 输出结果为:
# A的func执行
# B扩展的func执行

上面程序中,A是父类,B是A的子类,我们在A类中重定义了func()方法,在B类中重新定义了func()方法,在方法中通过super().func()又调用了父类的方法,所以执行结果才会有A类func()方法输出。

如果经常看Python内置库及第三方库源码的话,你会发现,super用的非常多的地方是在子类中调用父类的初始化__init__()方法,这种用法非常常见。

class A:
    def __init__(self, x):
        self.x = x

class B(A):

    def __init__(self, x, y):
        super().__init__(x)
        self.y = y
    

b = B(1, 2)
print(b.x, b.y)

看到这,你会想到super就是用来获取父类并用来调用父类方法的,这样说对不对呢,其实是不对的,使用supper获取的不是父类,而是MRO列表中的下一个类,所谓MRO列表即方法解析顺序(Method Resolution Order)列表,它代表着类继承的顺序,我们可以使用以下几种获得某个类的MRO列表:

C.mro()
C.__mro__
c.__class__.__mro__

MRO列表的顺序确定经历了很多次的变迁,最新的是通过C3线性化算法来实现的,感兴趣的话可以自行了解一下,总的来说,一个类的MRO列表就是合并所有父类的MRO列表,并遵循以下三条原则:

  • 子类永远在父类前面
  • 如果有多个父类,会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

下面来看一下下面这个例子:

class A(Base):
    def func(self):
        print("A的func执行")
        super().func()
        print("A的func执行完毕")


class B(Base):
    def func(self):
        print("B的func执行")
        super().func()
        print("B的func执行完毕")

class C(A, B):
    def func(self):
        print("C的func执行")
        super().func()
        print("C的func执行完毕")


c = C()
c.func()
# 获取MRO列表
print(c.__class__.__mro__)

执行结果如下:

python中super()函数的理解与基本使用

上述程序中,Base是父类,A、B都继承自Base,C继承自 A、B,它们的继承关系就是一个典型的菱形继承,如下:

通过结果我们可以看出,super并不是获取父类并用来调用父类的方法,而是根据MRO列表一次调用下一个类,使用c.__class__.__mro__可以获取MRO列表,MRO列表的顺序是C、A、B、Base、object。

super的原理

super计算方法解析顺序中的下一个类,可以接收两个参数:

def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]
  • 通过inst负责生成MRO列表
  • 通过cls定位在MRO列表中的index, 并返回mro[index + 1]

Python super()使用注意事项

Python 中,由于基类不会在 __init__() 中被隐式地调用,需要程序员显式调用它们。这种情况下,当程序中包含多重继承的类层次结构时,使用 super 是非常危险的,往往会在类的初始化过程中出现问题。

混用super与显式类调用

分析如下程序,C 类使用了 __init__() 方法调用它的基类,会造成 B 类被调用了 2 次:

class A:
    def __init__(self):
        print("A",end=" ")
        super().__init__()
class B:
    def __init__(self):
        print("B",end=" ")
        super().__init__()
class C(A,B):
    def __init__(self):
        print("C",end=" ")
        A.__init__(self)
        B.__init__(self)
print("MRO:",[x.__name__ for x in C.__mro__])
C()

运行结果为:

MRO: ['C', 'A', 'B', 'object']
C A B B

出现以上这种情况的原因在于,C 的实例调用 A.__init__(self),使得 super(A,self).__init__() 调用了 B.__init__() 方法。换句话说,super 应该被用到整个类的层次结构中。

但是,有时这种层次结构的一部分位于第三方代码中,我们无法确定外部包的这些代码中是否使用 super(),因此,当需要对某个第三方类进行子类化时,最好查看其内部代码以及 MRO 中其他类的内部代码。

不同种类的参数

使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名,一个类怎么能调用其基类的 __init__() 代码呢?这会导致下列问题:

class commonBase:
    def __init__(self):
        print("commonBase")
        super().__init__()
class base1(commonBase):
    def __init__(self):
        print("base1")
        super().__init__()
class base2(commonBase):
    def __init__(self):
        print("base2")
        super().__init__()
class myClass(base1,base2):
    def __init__(self,arg):
        print("my base")
        super().__init__(arg)
myClass(10)

运行结果为:

my base
Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\demo.py", line 20, in <module>
    myClass(10)
  File "C:\Users\mengma\Desktop\demo.py", line 19, in __init__
    super().__init__(arg)
TypeError: __init__() takes 1 positional argument but 2 were given

一种解决方法是使用 *args 和 **kwargs 包装的参数和关键字参数,这样即使不使用它们,所有的构造函数也会传递所有参数,如下所示:

class commonBase:
    def __init__(self,*args,**kwargs):
        print("commonBase")
        super().__init__()
class base1(commonBase):
    def __init__(self,*args,**kwargs):
        print("base1")
        super().__init__(*args,**kwargs)
class base2(commonBase):
    def __init__(self,*args,**kwargs):
        print("base2")
        super().__init__(*args,**kwargs)
class myClass(base1,base2):
    def __init__(self,arg):
        print("my base")
        super().__init__(arg)
myClass(10)

运行结果为:

my base
base1
base2
commonBase

不过,这是一种很糟糕的解决方法,由于任何参数都可以传入,所有构造函数都可以接受任何类型的参数,这会导致代码变得脆弱。另一种解决方法是在 MyClass 中显式地使用特定类的 __init__() 调用,但这无疑会导致第一种错误。

总结

现在我们知道:supper获取的是MRO列表中的下一个类,当前类的父类没有实质性的关系;还有如何查看MRO列表。最后需要注意的是super以及MRO列表,针对都是Python新式类!

英语好的话可以读一下这边文章Python's super() considered super

到此这篇关于python中super()函数的理解与基本使用的文章就介绍到这了,更多相关python中super()函数内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python显示进度条的方法
Sep 20 Python
Python中字典映射类型的学习教程
Aug 20 Python
python中常用的九种预处理方法分享
Sep 11 Python
Python单体模式的几种常见实现方法详解
Jul 28 Python
python with提前退出遇到的坑与解决方案
Jan 05 Python
Python实现求数列和的方法示例
Jan 12 Python
Python多进程并发与多线程并发编程实例总结
Feb 08 Python
Python hashlib模块用法实例分析
Jun 12 Python
解决tensorflow测试模型时NotFoundError错误的问题
Jul 27 Python
python爬虫项目设置一个中断重连的程序的实现
Jul 26 Python
详解Python+OpenCV绘制灰度直方图
Mar 22 Python
Python matplotlib绘制雷达图
Apr 13 Python
python自动化操作之动态验证码、滑动验证码的降噪和识别
Aug 30 #Python
Python图片验证码降噪和8邻域降噪
Aug 30 #Python
Python音乐爬虫完美绕过反爬
Aug 30 #Python
详解解Django 多对多表关系的三种创建方式
Aug 23 #Python
一些让Python代码简洁的实用技巧总结
Aug 23 #Python
一篇文章搞懂python混乱的切换操作与优雅的推导式
Aug 23 #Python
Python学习开发之图形用户界面详解
Aug 23 #Python
You might like
PHP CURL模拟GET及POST函数代码
2010/04/25 PHP
php验证手机号码(支持归属地查询及编码为UTF8)
2013/02/01 PHP
通过table标签,PHP输出EXCEL的实现方法
2013/07/24 PHP
Laravel使用Caching缓存数据减轻数据库查询压力的方法
2016/03/15 PHP
php简单备份与还原MySql的方法
2016/05/09 PHP
Laravel实现定时任务的示例代码
2017/08/10 PHP
PHP __call()方法实现委托示例
2019/05/20 PHP
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
JavaScript模块随意拖动示例代码
2014/05/27 Javascript
JS简单限制textarea内输入字符数量的方法
2015/10/14 Javascript
浅谈node.js中async异步编程
2015/10/22 Javascript
js判断上传文件后缀名是否合法
2016/01/28 Javascript
深入浅析javascript中的作用域(推荐)
2016/07/19 Javascript
js实现右键自定义菜单
2016/12/03 Javascript
详解用原生JavaScript实现jQuery的某些简单功能
2016/12/19 Javascript
vue中过滤器filter的讲解
2019/01/21 Javascript
过滤器vue.filters的使用方法实现
2019/09/18 Javascript
JavaScript类的继承多种实现方法
2020/05/30 Javascript
vue 页面回退mounted函数不执行的解决方案
2020/07/26 Javascript
使用Python脚本将Bing的每日图片作为桌面的教程
2015/05/04 Python
Python实现多并发访问网站功能示例
2017/06/19 Python
python 列表,数组,矩阵两两转换tolist()的实例
2018/04/04 Python
Python读取本地文件并解析网页元素的方法
2018/05/21 Python
加热夹克:RAVEAN
2018/10/19 全球购物
Internal修饰符有什么含义
2013/07/10 面试题
自我鉴定的范文
2013/10/03 职场文书
《陋室铭》教学反思
2014/02/26 职场文书
三方协议书范本
2014/04/22 职场文书
教师业务培训方案
2014/05/01 职场文书
会计人员岗位职责
2015/02/03 职场文书
2015入党个人自传范文
2015/06/26 职场文书
详解Go语言运用广度优先搜索走迷宫
2021/06/23 Python
Python快速实现一键抠图功能的全过程
2021/06/29 Python
如何解决php-fpm启动不了问题
2021/11/17 PHP
WebRTC记录音视频流(web技术分享)
2022/02/24 Javascript
CentOS下安装Jenkins的完整步骤
2022/04/07 Servers