python中super()函数的理解与基本使用
作者:tigeriaf 发布时间:2023-07-02 08:00:33
目录
前言
super的用法
super的原理
Python super()使用注意事项
混用super与显式类调用
不同种类的参数
总结
前言
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__)
执行结果如下:
上述程序中,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
来源:https://juejin.cn/post/6999789031433175071


猜你喜欢
- 简介EXISTS用于检查子查询是否至少会返回一行数据,该子查询实际上并不返回任何数据,而是返回值True或False。EXISTS 指定一个
- 自定义事件也可以用来创建自定义的表单输入组件,使用 v-model 来进行数据双向绑定。所以要让组件的 v-model 生效,它必须:接受一
- 本文实例展示了Python Tkinter实现简单布局的方法,示例中备有较为详尽的注释,便于读者理解。分享给大家供大家参考之用。具体如下:#
- 数据库并行访问,也就是两个或两以上用户同时访问同一数据,这也是数据库引擎如何设计和实现适度反应所面临的最大问题。设计优良、性能卓越的数据库引
- 在我们学习的过程中会遇到这么样的问题,就是在我们学习的过程中会发现需要分页处理,这里呢,给大家介绍书上说的分页。@app.route(
- 在创建SQL Server 2000 故障转移群集之前,必须配置 Microsoft 群集服务 (MSCS) 并使用 Microsoft W
- 不止一次在微信、知乎有读者朋友跑过来问:看完了基础书,甚至看两遍了,但自己写的时候还是没思路,我该怎么办?编程在我看来就是一门手艺活,绝不是
- 前言: 经过前面文章学习,我们知道 binlog 会记录数据库所有执行的 DDL 和 DML 语句(除了数据查询语句select、
- 对于yolo系列,应用广泛,在win10端也有很大的应用需求,所以这篇文章给出win10环境下的安装教程。先给出系列文章win10+anac
- 1. 游戏是更注重于体验的产品,所以应该将游戏本省做得更加炫动和增加参与感觉。2. 网络游戏和单击游戏的区别在于社会化的添加,所以运用好这样
- 本月的每月挑战会主题是NLP,我们会在本文帮你开启一种可能:使用pandas和python的自然语言工具包分析你Gmail邮箱中的内容。NL
- 一、Tornado简介Tornado 是 FriendFeed 的 Web 服务器及其常用工具的开源版本。Tornado 和现在的主流 We
- 笔者需要tensorflow仅运行在一个GPU上(机器本身有多GPU),而且需要依据系统参数动态调节,故无法简单使用CUDA_VISIBLE
- 在上次的爬虫中,抓取的数据主要用到的是第三方的Beautifulsoup库,然后对每一个具体的数据在网页中的selecter来找到它,每一个
- 在上一篇文章《Python教程—模拟网页点击爬虫定位系统》讲解怎么通过模拟点击方式爬取车辆定位数据,本次介绍怎么以模拟点击方式进入交管121
- python的版本及依赖的库的安装#版本python 3.7.1pip install pywin32==224pip install nu
- 在很多的情况下,在编写存储过程中往往会用到数组,但是mysql中存储过程传入参数并没有可以直接传入数组的方法。在这种情况下我们只能退而求之或
- 1.实现效果2.实现原理echarts官网:series-lines注意:流动特效只支持非平滑曲线(smooth:false)series-
- 问题你想将一个模块分割成多个文件。但是你不想将分离的文件统一成一个逻辑模块时使已有的代码遭到破坏。解决方案程序模块可以通过变成包来分割成多个
- RSA加密算法简史RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leo