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牛刀小试密码爆破
Feb 03 Python
python处理大数字的方法
May 27 Python
Python判断列表是否已排序的各种方法及其性能分析
Jun 20 Python
Python中的上下文管理器和with语句的使用
Apr 17 Python
pandas筛选某列出现编码错误的解决方法
Nov 07 Python
python实现贪吃蛇游戏
Mar 21 Python
使用python实现抓取腾讯视频所有电影的爬虫
Apr 15 Python
安装PyInstaller失败问题解决
Dec 14 Python
Keras 利用sklearn的ROC-AUC建立评价函数详解
Jun 15 Python
工程师必须了解的LRU缓存淘汰算法以及python实现过程
Oct 15 Python
详解python polyscope库的安装和例程
Nov 13 Python
告别网页搜索!教你用python实现一款属于自己的翻译词典软件
Jun 03 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
phpinfo 系统查看参数函数代码
2009/06/05 PHP
jQuery中的RadioButton,input,CheckBox取值赋值实现代码
2014/02/18 PHP
Zend Framework常用校验器详解
2016/12/09 PHP
PHP观察者模式原理与简单实现方法示例
2017/08/25 PHP
PHP的JSON封装、转变及输出操作示例
2019/09/27 PHP
如何阻止复制剪切和粘贴事件为了表单内容的安全
2013/05/23 Javascript
javascript:void(0)的作用示例介绍
2013/10/28 Javascript
关闭浏览器窗口弹出提示框并且可以控制其失效
2014/04/15 Javascript
动态加载iframe时get请求传递中文参数乱码解决方法
2014/05/07 Javascript
node.js中的fs.fsync方法使用说明
2014/12/15 Javascript
简介JavaScript中的getUTCFullYear()方法的使用
2015/06/10 Javascript
不得不分享的JavaScript常用方法函数集(下)
2015/12/25 Javascript
Bootstarp风格的toggle效果分享
2016/02/23 Javascript
jquery对dom节点的操作【推荐】
2016/04/15 Javascript
JavaScript进阶(三)闭包原理与用法详解
2020/05/09 Javascript
Vue切换Tab动态渲染组件的操作
2020/09/21 Javascript
Python3中多线程编程的队列运作示例
2015/04/16 Python
python操作ssh实现服务器日志下载的方法
2015/06/03 Python
Python中的条件判断语句与循环语句用法小结
2016/03/21 Python
Python实现将一个正整数分解质因数的方法分析
2017/12/14 Python
深入理解Python爬虫代理池服务
2018/02/28 Python
Python3利用Dlib实现摄像头实时人脸检测和平铺显示示例
2019/02/21 Python
Python安装及Pycharm安装使用教程图解
2019/09/20 Python
LTD Commodities:礼品,独特发现,家居装饰,家用器皿
2017/08/11 全球购物
购买美国制造的相框和画框架:Picture Frames
2018/08/14 全球购物
Java中会存在内存泄漏吗,请简单描述
2016/12/22 面试题
工商管理专业学生的自我评价
2013/10/01 职场文书
银行实习自我鉴定
2013/10/12 职场文书
2014年教研活动总结范文
2014/04/26 职场文书
保险公司开门红口号
2014/06/21 职场文书
医学专业大学生职业生涯规划书
2014/10/25 职场文书
2016年3月份红领巾广播稿
2015/12/21 职场文书
Python爬虫:从m3u8文件里提取小视频的正确操作
2021/05/14 Python
SpringBoot 拦截器妙用你真的了解吗
2021/07/01 Java/Android
Node-Red实现MySQL数据库连接的方法
2021/08/07 MySQL
JavaScript实现音乐播放器
2022/08/14 Javascript