Python中super函数用法实例分析


Posted in Python onMarch 18, 2019

本文实例讲述了Python中super函数用法。分享给大家供大家参考,具体如下:

这是个高大上的函数,在python装13手册里面介绍过多使用可显得自己是高手 23333. 但其实他还是很重要的. 简单说, super函数是调用下一个父类(超类)并返回该父类实例的方法. 这里的下一个的概念参考后面的MRO表介绍.

help介绍如下:

super(type, obj) -> bound super object; requires isinstance(obj, type)
super(type) -> unbound super object
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(C, self).meth(arg)

由此可知, super有三种用法, 第一参数总是召唤父类的那个类, 第二参数可缺(返回非绑定父类对象),也可以是实例对象或该类的子类. 最终返回的都是父类的实例(绑定或非绑定). 在Python3中,super函数多了一种用法是直接super(),相当于super(type,首参), 这个首参就是一般的传入的self实例本身啦. 因为在py2里面常用也是这种写法.

另外, 在py2中, super只支持新类( new-style class, 就是继承自object的).

为什么要调用父类?

在类继承时, 要是重定义某个方法, 这个方法就会覆盖掉父类的相应同名方法. 通过调用父类实例, 可以在子类中同时实现父类的功能.例如:

# Should be new-class based on object in python2.
class A(object):
 def __init__(self):
  print "enter A"
  print "leave A"
class B(A):
 def __init__(self):
  print "enter B"
  super(B, self).__init__()
  print "leave B"
>>> b = B()
enter B
enter A
leave A
leave B

通过调用super获得父类实例从而可以实现该实例的初始化函数. 这在实践中太常用了 (因为要继承父类的功能, 又要有新的功能).

直接使用父类来调用的差异

事实上, 上面的super函数方法还可以这么写:

class A(object):
 def __init__(self):
  print "enter A"
  print "leave A"
class B(A):
 def __init__(self):
  print "enter B"
  A.__init__(self)
  print "leave B"

通过直接使用父类类名来调用父类的方法, 实际也是可行的. 起码在上面的例子中效果上他们现在是一样的. 这种方法在老式类中也是唯一的调用父类的方法 (老式类没有super).

通过父类类名调用方法很常用, 比较直观. 但其效果和super还是有差异的. 例如:

class A(object):
 def __init__(self):
  print "enter A"
  print "leave A"
class B(A):
 def __init__(self):
  print "enter B"
  A.__init__(self)
  print "leave B"
class C(A):
 def __init__(self):
  print "enter C"
  A.__init__(self)
  print "leave C"
class D(B,C):
 def __init__(self):
  print "enter D"
  B.__init__(self)
  C.__init__(self)
  print "leave D"
>>> d=D()
enter D
enter B
enter A
leave A
leave B
enter C
enter A
leave A
leave C
leave D

可以发现, 这里面A的初始化函数被执行了两次. 因为我们同时要实现B和C的初始化函数, 所以分开调用两次, 这是必然的结果.

但如果改写成super呢?

class A(object):
 def __init__(self):
  print "enter A"
  print "leave A"
class B(A):
 def __init__(self):
  print "enter B"
  super(B,self).__init__()
  print "leave B"
class C(A):
 def __init__(self):
  print "enter C"
  super(C,self).__init__()
  print "leave C"
class D(B,C):
 def __init__(self):
  print "enter D"
  super(D,self).__init__()
  print "leave D"
>>> d=D()
enter D
enter B
enter C
enter A
leave A
leave C
leave B
leave D

会发现所有父类ABC只执行了一次, 并不像之前那样执行了两次A的初始化.

然后, 又发现一个很奇怪的: 父类的执行是 BCA 的顺序并且是全进入后再统一出去. 这是MRO表问题, 后面继续讨论.

如果没有多继承, super其实和通过父类来调用方法差不多. 但, super还有个好处: 当B继承自A, 写成了A.__init__, 如果根据需要进行重构全部要改成继承自 E,那么全部都得改一次! 这样很麻烦而且容易出错! 而使用super()就不用一个一个改了(只需类定义中改一改就好了)

Anyway, 可以发现, super并不是那么简单.

MRO 表

MRO是什么? 可以通过以下方式调出来:

>>> D.mro() # or d.__class__.mro() or D.__class__.mro(D) 
[D, B, C, A, object]
>>> B.mro()
[B, A, object]
>>> help(D.mro)
#Docstring:
#mro() -> list
#return a type's method resolution order
#Type:  method_descriptor

MRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表 (类继承顺序表去理解也行) 啦.

这个表有啥用? 首先了解实际super做了啥:

def super(cls, inst):
 mro = inst.__class__.mro()
 return mro[mro.index(cls) + 1]

换而言之, super方法实际是调用了cls的在MRO表中的下一个类. 如果是简单一条线的单继承, 那就是父类->父类一个一个地下去罗. 但对于多继承, 就要遵循MRO表中的顺序了. 以上面的D的调用为例:

d的初始化

-> D (进入D) super(D,self)
-> 父类B (进入B) super(B,self)
-> 父类C (进入C) super(C,self)
-> 父父类A (进入A)  (退出A) # 如有继续super(A,self)  -> object (停了)
-> (退出C)
-> (退出B)
-> (退出D)

所以, 在MRO表中的超类初始化函数只执行了一次!

那么, MRO的顺序究竟是怎么定的呢? 这个可以参考官方说明The Python 2.3 Method Resolution Order . 基本就是, 计算出每个类(从父类到子类的顺序)的MRO, 再merge 成一条线. 遵循以下规则:

在 MRO 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。 这个原则包括两点:

1. 基类永远在派生类后面
2. 类定义时的继承顺序影响相对顺序.

如果有以下继承 (Python中的super用法详解):

    object
     /   \
    /      A
   |     /   \
  B-1  C-2   D-2
    \   /    /
     E-1    /
        \  /
          F

那么MRO是: F -> E -> B -> C -> D -> A -> object

怎么解释呢?

根据官方的方法, 是:

L(O) = O
L(B) = B O
L(A) = A O
L(C) = C A O
L(D) = D A O
L(E) = E + merge(L(B),L(C))
  = E + merge(BO,CAO)
  = E + B + merge(O,CAO)
  = E + B + C + merge(O,AO)
  = E + B + C + A + merge(O,O)
  = E B C A O
L(F) = F + merge(L(E),L(D))
  = F + merge(EBCAO,DAO)
  = F + EBC + merge(AO,DAO)
  = F + EBC + D + merge(AO,AO)
  = F EBC D AO

看起来很复杂..但还是遵循在 MRO 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。所以, 我个人认为可以这么想:

  • 先找出最长深度最深的继承路线F->E->C->A->object. (因为必然基类永远出现在派生类后面)
  • 类似深度优先, 定出其余顺序: F->E->B->obj, F->D->A-object
  • 如果有多个基类,基类的相对顺序保持不变, 类似于merge时优先提前面的项. 所以排好这些路线: (FEBO, FECAO, FDAO)
  • F->E->B->obj且E(B,C)决定B在C前面.所以F->E->B->C->A->obj (相当于F+merge(EBO,ECAO)).
  • F->D->A-object且F(E,D)决定了D在E后, 所以D在E后A前. 因为相对顺序, 相当于FE+merge(BCAO, DAO), 所以FE BC D AO

更多可参考

  • Raymond Hettinger 的Python's super() considered super! (据说很经典的讨论)
  • James Knight 的 Python's Super Considered Harmful
  • Py3 cookbook: 8.7 调用父类方法http://python3-cookbook.readthedocs.org/zh_CN/latest/c08/p07_calling_method_on_parent_class.html
  • Python编程中对super函数的正确理解和用法解析

关于Python相关内容感兴趣的读者可查看本站专题:《Python面向对象程序设计入门与进阶教程》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python编码操作技巧总结》及《Python入门与进阶经典教程》

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
Python函数中的函数(闭包)用法实例
Mar 15 Python
python结合selenium获取XX省交通违章数据的实现思路及代码
Jun 26 Python
基于python中staticmethod和classmethod的区别(详解)
Oct 24 Python
Python编程django实现同一个ip十分钟内只能注册一次
Nov 03 Python
Python实现基本数据结构中队列的操作方法示例
Dec 04 Python
Python实现的NN神经网络算法完整示例
Jun 19 Python
Python之csv文件从MySQL数据库导入导出的方法
Jun 21 Python
Python时间和字符串转换操作实例分析
Mar 16 Python
Python 实现使用空值进行赋值 None
Mar 12 Python
keras 多任务多loss实例
Jun 22 Python
七个非常实用的Python工具包总结
Jun 15 Python
python自动化八大定位元素讲解
Jul 09 Python
Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】
Mar 18 #Python
python+pyqt5编写md5生成器
Mar 18 #Python
python+pyqt5实现图片批量缩放工具
Mar 18 #Python
Python实现从SQL型数据库读写dataframe型数据的方法【基于pandas】
Mar 18 #Python
PyQt5实现简易计算器
May 30 #Python
Python实现简单层次聚类算法以及可视化
Mar 18 #Python
PyQt5实现简单数据标注工具
Mar 18 #Python
You might like
SONY ICF-F10中波修复记
2021/03/02 无线电
如何分别全角和半角以避免乱码
2006/10/09 PHP
PHP版国家代码、缩写查询函数代码
2011/08/14 PHP
浅谈php优化需要注意的地方
2014/11/27 PHP
PHP时间和日期函数详解
2015/05/08 PHP
php构造函数与析构函数
2016/04/23 PHP
PHPstorm快捷键(分享)
2017/07/17 PHP
PHP性能分析工具xhprof的安装使用与注意事项
2017/12/19 PHP
Javascript中判断变量是数组还是对象(array还是object)
2013/08/14 Javascript
通过AJAX的JS、JQuery两种方式解析XML示例介绍
2013/09/23 Javascript
js Date概念详细介绍
2013/11/22 Javascript
js实现表格字段排序
2014/02/19 Javascript
整理Javascript流程控制语句学习笔记
2015/11/29 Javascript
微信小程序实现留言板功能
2018/11/02 Javascript
Element 默认勾选表格 toggleRowSelection的实现
2019/09/04 Javascript
layer.js open 隐藏滚动条的例子
2019/09/05 Javascript
Vue开发环境跨域访问问题
2020/01/22 Javascript
js cavans实现静态滚动弹幕
2020/05/21 Javascript
解决VueCil代理本地proxytable无效报错404的问题
2020/11/07 Javascript
[03:12]2016完美“圣”典风云人物:单车专访
2016/12/02 DOTA
对Python中数组的几种使用方法总结
2018/06/28 Python
基于Python获取城市近7天天气预报
2019/11/26 Python
python ETL工具 pyetl
2020/06/07 Python
全球独特生活方式产品和礼品购物网站:AHAlife
2018/09/18 全球购物
Java软件工程师综合面试题笔试题
2013/09/08 面试题
社会学专业求职信
2014/02/24 职场文书
设计专业自荐信
2014/06/19 职场文书
工作说明书格式
2014/07/29 职场文书
年检委托书
2014/08/30 职场文书
学风建设演讲稿
2014/09/12 职场文书
2014年办公室主任工作总结
2014/11/12 职场文书
升职自荐信怎么写
2015/03/05 职场文书
经理岗位职责范本
2015/04/15 职场文书
被告代理词范文
2015/05/25 职场文书
Python Django框架介绍之模板标签及模板的继承
2021/05/27 Python
vue实现锚点定位功能
2021/06/29 Vue.js