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 相关文章推荐
分析python服务器拒绝服务攻击代码
Jan 16 Python
python中while循环语句用法简单实例
May 07 Python
利用numpy+matplotlib绘图的基本操作教程
May 03 Python
R vs. Python 数据分析中谁与争锋?
Oct 18 Python
ubuntu安装mysql pycharm sublime
Feb 20 Python
Python简单读写Xls格式文档的方法示例
Aug 17 Python
详解Django 时间与时区设置问题
Jul 23 Python
Python Request爬取seo.chinaz.com百度权重网站的查询结果过程解析
Aug 13 Python
python使用openCV遍历文件夹里所有视频文件并保存成图片
Jan 14 Python
Django+Celery实现动态配置定时任务的方法示例
May 26 Python
Linux安装Python3如何和系统自带的Python2并存
Jul 23 Python
一篇文章弄懂Python中的内建函数
Aug 07 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获取本周第一天和最后一天示例代码
2014/02/24 PHP
2014最热门的24个php类库汇总
2014/12/18 PHP
PHP5.2下preg_replace函数的问题
2015/05/08 PHP
php实现按天数、星期、月份查询的搜索框
2016/05/02 PHP
PHP中FTP相关函数小结
2016/07/15 PHP
php简单读取.vcf格式文件的方法示例
2017/09/02 PHP
jQuery中文入门指南,翻译加实例,jQuery的起点教程
2007/01/13 Javascript
利用jquery操作select下拉列表框的代码
2010/06/04 Javascript
js客户端快捷键管理类的较完整实现和应用
2010/06/08 Javascript
AngularJS语法详解
2015/01/23 Javascript
JS实现一个按钮的方法
2015/02/05 Javascript
JavaScript中setTimeout和setInterval函数的传参及调用
2016/03/11 Javascript
Vue.js Ajax动态参数与列表显示实现方法
2016/10/20 Javascript
js实现文字向上轮播功能
2017/01/13 Javascript
详解vue mint-ui源码解析之loadmore组件
2017/10/11 Javascript
微信小程序使用template标签实现五星评分功能
2018/11/03 Javascript
vue动态绑定class选中当前列表变色的方法示例
2018/12/19 Javascript
使用VUE+iView+.Net Core上传图片的方法示例
2019/01/04 Javascript
JS查找孩子节点简单示例
2019/07/25 Javascript
Echarts地图添加引导线效果(labelLine)
2019/09/30 Javascript
在Linux上安装Python的Flask框架和创建第一个app实例的教程
2015/03/30 Python
解析Mac OS下部署Pyhton的Django框架项目的过程
2016/05/03 Python
Python处理JSON时的值报错及编码报错的两则解决实录
2016/06/26 Python
Python 函数基础知识汇总
2018/03/09 Python
python如何求解两数的最大公约数
2018/09/27 Python
通过pycharm使用git的步骤(图文详解)
2019/06/13 Python
Python any()函数的使用方法
2019/10/28 Python
pytorch 实现模型不同层设置不同的学习率方式
2020/01/06 Python
CSS3关于z-index不生效问题的解决
2020/02/19 HTML / CSS
html5-canvas中使用clip抠出一个区域的示例代码
2018/05/25 HTML / CSS
HTML5学习笔记之History API
2015/02/26 HTML / CSS
美国围栏公司:Walpole Outdoors
2019/11/19 全球购物
函数只定义了一次, 调用了一次, 但编译器提示非法重定义了-什么问题?
2014/10/03 面试题
大三学习计划书范文
2014/05/02 职场文书
团队拓展训练心得体会
2016/01/12 职场文书
javascript函数式编程基础
2021/09/15 Javascript