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 append、extend与insert的区别
Oct 13 Python
解决Python字典写入文件出行首行有空格的问题
Sep 27 Python
Python复制Word内容并使用格式设字体与大小实例代码
Jan 22 Python
transform python环境快速配置方法
Sep 27 Python
利用python和ffmpeg 批量将其他图片转换为.yuv格式的方法
Jan 08 Python
详解python--模拟轮盘抽奖游戏
Apr 12 Python
解决Python中pandas读取*.csv文件出现编码问题
Jul 12 Python
Python3视频转字符动画的实例代码
Aug 29 Python
Python提取PDF内容的方法(文本、图像、线条等)
Sep 25 Python
Python控制台输出时刷新当前行内容而不是输出新行的实现
Feb 21 Python
基于python和flask实现http接口过程解析
Jun 15 Python
python实现数据结构中双向循环链表操作的示例
Oct 09 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中文截取函数mb_strlen和mb_substr
2015/02/09 PHP
PHP实现将优酷土豆腾讯视频html地址转换成flash swf地址的方法
2017/08/04 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
PHP基于DateTime类解决Unix时间戳与日期互转问题【针对1970年前及2038年后时间戳】
2018/06/13 PHP
thinkPHP框架动态配置用法实例分析
2018/06/14 PHP
Javascript 继承机制的实现
2009/08/12 Javascript
自写的一个jQuery圆角插件
2010/10/26 Javascript
js鼠标滑过弹出层的定位IE6bug解决办法
2012/12/26 Javascript
JavaScript获取图片的原始尺寸以宽度为例
2014/05/04 Javascript
jQuery中addClass()方法用法实例
2015/01/05 Javascript
javascript实现 百度翻译 可折叠的分享按钮列表
2015/03/12 Javascript
基于JavaScript判断浏览器到底是关闭还是刷新(超准确)
2016/02/01 Javascript
jQuery轻松实现表格的隔行变色和点击行变色的实例代码
2016/05/09 Javascript
js获取当前年月日-YYYYmmDD格式的实现代码
2016/06/01 Javascript
jquery实现图片切换代码
2016/10/13 Javascript
BootstrapTable请求数据时设置超时(timeout)的方法
2017/01/22 Javascript
基于Bootstrap漂亮简洁的CSS3价格表(附源码下载)
2017/02/28 Javascript
vue + socket.io实现一个简易聊天室示例代码
2017/03/06 Javascript
基于 D3.js 绘制动态进度条的实例详解
2018/02/26 Javascript
基于React+Redux的SSR实现方法
2018/07/03 Javascript
在小程序中使用腾讯视频插件播放教程视频的方法
2018/07/10 Javascript
React精髓!一篇全概括小结(急速)
2019/05/23 Javascript
Python使用迭代器捕获Generator返回值的方法
2017/04/05 Python
对Python中Iterator和Iterable的区别详解
2018/10/18 Python
讲解Python3中NumPy数组寻找特定元素下标的两种方法
2019/08/04 Python
python实现图片,视频人脸识别(dlib版)
2020/11/18 Python
详解css3使用transform出现字体模糊的解决办法
2020/10/16 HTML / CSS
解决canvas转base64/jpeg时透明区域变成黑色背景的方法
2016/10/23 HTML / CSS
介绍一下linux的文件系统
2012/03/20 面试题
行政办公员自我评价分享
2013/12/14 职场文书
城市轨道交通工程职业规划书范文
2014/01/18 职场文书
品质管理部岗位职责范文
2014/03/01 职场文书
财务部副经理岗位职责
2014/03/14 职场文书
泰山导游词
2015/02/02 职场文书
班级联欢会主持词
2015/07/03 职场文书
Linux在两个服务器直接传文件的操作方法
2022/08/05 Servers