Python中super()函数简介及用法分享


Posted in Python onJuly 11, 2016

首先看一下super()函数的定义:

super([type [,object-or-type]])

Return a **proxy object** that delegates method calls to a **parent or sibling** class of type.

返回一个代理对象, 这个对象负责将方法调用分配给第一个参数的一个父类或者同辈的类去完成.

parent or sibling class 如何确定?

第一个参数的__mro__属性决定了搜索的顺序, super指的的是 MRO(Method Resolution Order) 中的下一个类, 而不一定是父类!

super()和getattr() 都使用__mro__属性来解析搜索顺序, __mro__实际上是一个只读的元组.

MRO中类的顺序是怎么排的呢?

实际上MRO列表本身是根据一种C3的线性化处理技术确定的, 理论说明可以参考这里, 这里只简单说明一下原则:

在MRO中, 基类永远出现在派生类的后面, 如果有多个基类, 基类的相对顺序不变.

MRO实际上是对继承树做层序遍历的结果, 把一棵带有结构的树变成了一个线性的表, 所以沿着这个列表一直往上, 就可以无重复的遍历完整棵树, 也就解决了多继承中的Diamond问题.

比如说:

class Root:
  pass

class A(Root):
  pass

class B(Root):
  pass

class C(A, B):
  pass

print(C.__mro__)

# 输出结果为:
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>)

super()实际返回的是一个代理的super对象!

调用super()这个构造方法时, 只是返回一个super()对象, 并不做其他的操作.

然后对这个super对象进行方法调用时, 发生的事情如下:

找到第一个参数的__mro__列表中的下一个直接定义了该方法的类, 并实例化出一个对象
然后将这个对象的self变量绑定到第二个参数上, 返回这个对象

举个例子:

class Root:
  def __init__(self):
    print('Root')

class A(Root):
  def __init__(self):
    super().__init__() # 等同于super(A, self).__init__()

在A的构造方法中, 先调用super()得到一个super对象, 然后向这个对象调用init方法, 这是super对象会搜索A的__mro__列表, 找到第一个定义了__init__方法的类, 于是就找到了Root, 然后调用Root.__init__(self), 这里的self是super()的第二个参数, 是编译器自动填充的, 也就是A的__init__的第一个参数, 这样就完成对__init__方法调用的分配.

注意: 在许多语言的继承中, 子类必须调用父类的构造方法, 就是为了保证子类的对象能够填充上父类的属性! 而不是初始化一个父类对象...(我之前就一直是这么理解的..). Python中就好多了, 所谓的调用父类构造方法, 就是明明白白地把self传给父类的构造方法, 我的小身子骨就这么交给你了, 随便你怎么折腾吧:joy:

参数说明

super() -> same as super(__class__, <first argument>) # <first argument>指的是调用super的函数的第一个参数
super(type) -> unbound super object
super(type, obj) -> bound super object; requires isinstance(obj, type)
super(type, type2) -> bound super object; requires issubclass(type2, type)

 Typical use to call a cooperative superclass method:
  class C(B):
    def meth(self, arg):
      super().meth(arg)
  This works for class methods too:
  class C(B):
    @classmethod
    def cmeth(cls, arg):
      super().cmeth(arg)

如果提供了第二个参数, 则找到的父类对象的self就绑定到这个参数上, 后面调用这个对象的方法时, 可以自动地隐式传递self.
如果第二个参数是一个对象, 则isinstance(obj, type)必须为True. 如果第二个参数为一个类型, 则issubclass(type2, type)必须为True

如果没有传递第二个参数, 那么返回的对象就是Unbound, 调用这个unbound对象的方法时需要手动传递第一个参数, 类似于Base.__int__(self, a, b).

不带参数的super()只能用在类定义中(因为依赖于caller的第二个参数), 编译器会自动根据当前定义的类填充参数.
也就是说, 后面所有调用super返回对象的方法时, 第一个参数self都是super()的第二个参数. 因为Python中所谓的方法, 就是一个第一个参数为self的函数, 一般在调用方法的时候a.b()会隐式的将a赋给b()的第一个参数.

super()的两种常见用法:

单继承中, super用来指代隐式指代父类, 避免直接使用父类的名字
多继承中, 解决Diamond问题 (TODO)

对面向对象的理解

其实我觉得Python里面这样的语法更容易理解面向对象的本质, 比Java中隐式地传this更容易理解.

所谓函数, 就是一段代码, 接受输入, 返回输出. 所谓方法, 就是一个函数有了一个隐式传递的参数. 所以方法就是一段代码, 是类的所有实例共享的, 唯一不同的是各个实例调用的时候传给方法的this 或者self不一样而已.

构造方法是什么呢? 其实也是一个实例方法啊, 它只有在对象生成了之后才能调用, 所以Python中__init__方法的参数是self啊. 调用构造方法时其实已经为对象分配了内存, 构造方法只是起到初始化的作用, 也就是为这段内存里面赋点初值而已.

Java中所谓的静态变量其实也就是类的变量, 其实也就是为类也分配了内存, 里面存了这些变量, 所以Python中的类对象我觉得是很合理的, 也比Java要直观. 至于静态方法, 那就与对象一点关系都没有了, 本质就是个独立的函数, 只不过写在了类里面而已. 而Python中的classmethod其实也是一种静态方法, 不过它会依赖于cls对象, 这个cls就是类对象, 但是只要想用这个方法, 类对象必然是存在的, 不像实例对象一样需要手动的实例化, 所以classmethod也可以看做是一种静态变量. 而staticmethod就是真正的静态方法了, 是独立的函数, 不依赖任何对象.

Java中的实例方法是必须依赖于对象存在的, 因为要隐式的传输this, 如果对象不存在这个this也没法隐式了. 所以在静态方法中是没有this指针的, 也就没法调用实例方法. 而Python中的实例方法是可以通过类名来调用的, 只不过因为这时候self没办法隐式传递, 所以必须得显式地传递.

Python 相关文章推荐
shell命令行,一键创建 python 模板文件脚本方法
Mar 20 Python
opencv改变imshow窗口大小,窗口位置的方法
Apr 02 Python
python 提取tuple类型值中json格式的key值方法
Dec 31 Python
DRF跨域后端解决之django-cors-headers的使用
Jan 27 Python
Python API 自动化实战详解(纯代码)
Jun 11 Python
Django通过dwebsocket实现websocket的例子
Nov 15 Python
在Pytorch中计算卷积方法的区别详解(conv2d的区别)
Jan 03 Python
python+opencv实现移动侦测(帧差法)
Mar 20 Python
Anconda环境下Vscode安装Python的方法详解
Mar 29 Python
python numpy矩阵信息说明,shape,size,dtype
May 22 Python
解决pytorch下出现multi-target not supported at的一种可能原因
Feb 06 Python
Python3.8官网文档之类的基础语法阅读
Sep 04 Python
Swift中的协议(protocol)学习教程
Jul 08 #Python
Python中多线程的创建及基本调用方法
Jul 08 #Python
使用PyInstaller将Python程序文件转换为可执行程序文件
Jul 08 #Python
Python遍历目录中的所有文件的方法
Jul 08 #Python
Python常用的内置序列结构(列表、元组、字典)学习笔记
Jul 08 #Python
使用Python读写及压缩和解压缩文件的示例
Jul 08 #Python
Python中的FTP通信模块ftplib的用法整理
Jul 08 #Python
You might like
PHP基础知识介绍
2013/09/17 PHP
PHP实现生成唯一会员卡号
2015/08/24 PHP
老生常谈文本文件和二进制文件的区别
2017/02/27 PHP
非常好的js代码
2006/06/27 Javascript
[原创]图片分页查看
2006/08/28 Javascript
几个高效,简洁的字符处理函数
2007/04/12 Javascript
Code: write(s,d) 输出连续字符串
2007/08/19 Javascript
通过event对象的fromElement属性解决热区设置主实体的一个bug
2008/12/22 Javascript
jquery indexOf使用方法
2013/08/19 Javascript
JS建造者模式基本用法实例分析
2015/06/30 Javascript
分享网页检测摇一摇实例代码
2016/01/14 Javascript
require简单实现单页应用程序(SPA)
2016/07/12 Javascript
使用plupload自定义参数实现多文件上传
2016/07/19 Javascript
基于Bootstrap的Metronic框架实现页面链接收藏夹功能
2016/08/29 Javascript
Vue实现双向绑定的方法
2016/12/22 Javascript
Angular.js ng-file-upload结合springMVC的使用教程
2017/07/10 Javascript
利用angular、react和vue实现相同的面试题组件
2018/02/19 Javascript
angular6 利用 ngContentOutlet 实现组件位置交换(重排)
2018/11/02 Javascript
AutoJs实现刷宝短视频的思路详解
2020/05/22 Javascript
如何利用Node.js与JSON搭建简单的动态服务器
2020/06/16 Javascript
[02:46]2014DOTA2国际邀请赛 选手为你解读比赛MVP充满梦想
2014/07/09 DOTA
python处理图片之PIL模块简单使用方法
2015/05/11 Python
TensorFlow实现MLP多层感知机模型
2018/03/09 Python
Python实现的序列化和反序列化二叉树算法示例
2019/03/02 Python
Python使用QQ邮箱发送邮件实例与QQ邮箱设置详解
2020/02/18 Python
五款漂亮的纯CSS3动画按钮的实例教程
2014/11/21 HTML / CSS
美国真皮手袋品牌:GiGi New York
2017/03/10 全球购物
如何提高MySql的安全性
2014/06/19 面试题
幼儿园教师获奖感言
2014/03/11 职场文书
新闻稿标题
2015/07/18 职场文书
工作感言一句话
2015/08/01 职场文书
新学期感想
2015/08/10 职场文书
详解jQuery的核心函数和事件处理
2022/02/18 jQuery
动漫APP软件排行榜前十名,半次元上榜,第一款由腾讯公司推出
2022/03/18 杂记
Windows Server 2022 超融合部署(图文教程)
2022/06/25 Servers
Python如何利用pandas读取csv数据并绘图
2022/07/07 Python