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 RuntimeError: thread.__init__() not called解决方法
Apr 28 Python
python压缩文件夹内所有文件为zip文件的方法
Jun 20 Python
Django中的CACHE_BACKEND参数和站点级Cache设置
Jul 23 Python
Pycharm2017版本设置启动时默认自动打开项目的方法
Oct 29 Python
Windows 8.1 64bit下搭建 Scrapy 0.22 环境
Nov 18 Python
python协程gevent案例 爬取斗鱼图片过程解析
Aug 27 Python
Python统计文本词汇出现次数的实例代码
Feb 27 Python
解决python cv2.imread 读取中文路径的图片返回为None的问题
Jun 02 Python
python和js交互调用的方法
Jun 23 Python
如何用Python进行时间序列分解和预测
Mar 01 Python
Python爬虫:从m3u8文件里提取小视频的正确操作
May 14 Python
Python读取和写入Excel数据
Apr 20 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
基于mysql的bbs设计(二)
2006/10/09 PHP
php session劫持和防范的方法
2013/11/12 PHP
一个经典实用的PHP图像处理类分享
2014/11/18 PHP
PHP实现XML与数据格式进行转换类实例
2015/07/29 PHP
Laravel 创建可以传递参数 Console服务的例子
2019/10/14 PHP
PHP+Redis链表解决高并发下商品超卖问题(实现原理及步骤)
2020/08/03 PHP
dwr spring的集成实现代码
2009/03/22 Javascript
浅析JS刷新框架中的其他页面 &amp;&amp; JS刷新窗口方法汇总
2013/07/08 Javascript
Extjs4中的分页应用结合前后台
2013/12/13 Javascript
javaScript如何处理从java后台返回的list
2014/04/24 Javascript
JS获取随机数函数可自定义最小值最大值
2014/05/08 Javascript
PHP和NodeJs开发的应用如何共用Session
2015/04/16 NodeJs
关于angularJs指令的Scope(作用域)介绍
2016/10/25 Javascript
轻松实现jQuery添加删除按钮Click事件
2017/03/13 Javascript
重新认识vue之事件阻止冒泡的实现
2018/08/02 Javascript
如何利用vue+vue-router+elementUI实现简易通讯录
2019/05/13 Javascript
Vue.js中的高级面试题及答案
2020/01/13 Javascript
javascript实现移动端红包雨页面
2020/06/23 Javascript
Vue切换Tab动态渲染组件的操作
2020/09/21 Javascript
前端 javascript 实现文件下载的示例
2020/11/24 Javascript
详解Python设计模式编程中观察者模式与策略模式的运用
2016/03/02 Python
python 遍历字符串(含汉字)实例详解
2017/04/04 Python
Python实现字符串逆序输出功能示例
2017/06/24 Python
python3中os.path模块下常用的用法总结【推荐】
2018/09/16 Python
Python匿名函数/排序函数/过滤函数/映射函数/递归/二分法
2019/06/05 Python
详解win10下pytorch-gpu安装以及CUDA详细安装过程
2021/01/28 Python
利用简洁的图片预加载组件提升html5移动页面的用户体验
2016/03/11 HTML / CSS
世界顶级俱乐部的官方球衣和套装:Subside Sports
2018/04/22 全球购物
瑞士领先的网上超市:LeShop.ch
2018/11/14 全球购物
德国受欢迎的旅游和休闲网站:lastminute.de
2019/09/23 全球购物
J2EE中的容器都包括哪些
2013/08/21 面试题
学生感冒英文请假条
2014/02/04 职场文书
小学开学典礼主持词
2014/03/19 职场文书
2014年租房协议书范本
2014/10/30 职场文书
2016年国庆节宣传标语
2015/11/25 职场文书
python前后端自定义分页器
2022/04/13 Python