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使用新浪微博API发送微博的例子
Apr 10 Python
Python使用Beautiful Soup包编写爬虫时的一些关键点
Jan 20 Python
使用python装饰器计算函数运行时间的实例
Apr 21 Python
numpy使用fromstring创建矩阵的实例
Jun 15 Python
Linux下python与C++使用dlib实现人脸检测
Jun 29 Python
python实现图片批量压缩程序
Jul 23 Python
python+ffmpeg批量去视频开头的方法
Jan 09 Python
浅谈python中统计计数的几种方法和Counter详解
Nov 07 Python
Python爬取爱奇艺电影信息代码实例
Nov 26 Python
Python一行代码解决矩阵旋转的问题
Nov 30 Python
使用PyQt5实现图片查看器的示例代码
Apr 21 Python
Python Pillow(PIL)库的用法详解
Sep 19 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读取csv实现csv文件下载功能
2013/12/18 PHP
php调用google接口生成二维码示例
2014/04/28 PHP
你应该知道PHP浮点数知识
2015/05/13 PHP
在Win2003(64位)中配置IIS6+PHP5.2.17+MySQL5.5的运行环境
2016/04/04 PHP
JavaScript isPrototypeOf和hasOwnProperty使用区别
2010/03/04 Javascript
关于this和self的使用说明
2010/08/01 Javascript
jQuery 瀑布流 绝对定位布局(二)(延迟AJAX加载图片)
2012/05/23 Javascript
鼠标悬浮停留三秒后自动显示大图js代码
2014/09/09 Javascript
JavaScript仿静态分页实现方法
2015/08/04 Javascript
Nodejs如何复制文件
2016/03/09 NodeJs
js 定义对象数组(结合)多维数组方法
2016/07/27 Javascript
Bootstrap源码解读导航(6)
2016/12/23 Javascript
jQuery表单插件ajaxForm实例详解
2017/01/17 Javascript
Nodejs高扩展性的模板引擎 functmpl简介
2017/02/13 NodeJs
Node.js+Express+MySql实现用户登录注册功能
2017/07/10 Javascript
VueJs单页应用实现微信网页授权及微信分享功能示例
2017/07/26 Javascript
详解webpack的配置文件entry与output
2017/08/21 Javascript
vue实现裁切图片同时实现放大、缩小、旋转功能
2018/03/02 Javascript
vue init webpack 建vue项目报错的解决方法
2018/09/29 Javascript
JS实现普通轮播图特效
2020/01/01 Javascript
javascript实现京东登录显示隐藏密码
2020/08/02 Javascript
在Python中使用PIL模块处理图像的教程
2015/04/29 Python
Python实现八大排序算法
2016/08/13 Python
怎么使用pipenv管理你的python项目
2018/03/12 Python
django 多对多表的创建和插入代码实现
2019/09/09 Python
西班牙在线药店:DosFarma
2020/03/28 全球购物
C#中有没有静态构造函数,如果有是做什么用的?
2016/06/04 面试题
铲车司机岗位职责
2014/03/15 职场文书
委托证明模板
2014/09/16 职场文书
假期安全教育广播稿
2014/10/04 职场文书
新教师个人工作总结
2015/02/06 职场文书
go 实现简易端口扫描的示例
2021/05/22 Golang
Go语言特点及基本数据类型使用详解
2022/03/21 Golang
详解OpenCV获取高动态范围(HDR)成像
2022/04/29 Python
Python使用Beautiful Soup(BS4)库解析HTML和XML
2022/06/05 Python
ubuntu端向日葵键盘输入卡顿问题及解决
2022/12/24 Servers