[python] super() 用法
问题的发现与提出
在Python类的方法(method)中,要调用父类的某个方法,在Python 2.2以前,通常的写法如下:
class A:
def __init__(self):
print "enter A"
print "leave A"
class B(A):
def __init__(self):
print "enter B"
A.__init__(self)
print "leave B"
>>> b = B()
enter B
enter A
leave A
leave B
即,使用非绑定的类方法(用类名来引用的方法),并在参数列表中,引入待绑定的对象(self),从而达到调用父类的目的。
这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来,例如:
class B(C): # A --> C
def __init__(self):
print "enter B"
C.__init__(self) # A --> C
print "leave B"
如果代码简单,这样的改动或许还可以接受。但如果代码量庞大,这样的修改可能是灾难性的。因此,自Python 2.2开始,Python添加了一个关键字super,来解决这个问题。下面是Python 2.3的官方文档说明:
super(type[, object-or-type])
Return the superclass of type. If the second argument is omitted the super object
returned is unbound. If the second argument is an object, isinstance(obj, type)
must be true. If the second argument is a type, issubclass(type2, type) must be
true. super() only works for new-style classes.
A typical use for calling a cooperative superclass method is:
class C(B):
def meth(self, arg):
super(C, self).meth(arg)
为什么要调用父类?
在类继承时, 要是重定义某个方法, 这个方法就会覆盖掉父类的相应同名方法. 通过调用父类实例, 可以在子类中同时实现父类的功能.例如:
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"
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表问题,后面继续讨论。
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 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。这个原则包括两点:
- 基类永远在派生类后面
- 类定义时的继承顺序影响相对顺序.
如果有以下继承:
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 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。
reference:
- Raymond Hettinger 的Python’s super() considered super! (据说很经典的讨论)
- MRO与C3详解
- MRO图解
- Py3 cookbook: 8.7 调用父类方法
[python] super() 用法的更多相关文章
- python super用法
普通继承 class FooParent(object): def __init__(self): self.parent = 'I\'m the parent.' print 'Parent' de ...
- Python中的super()用法
Python中对象方法的定义很怪异,第一个参数一般都命名为self(相当于其它语言的this,比如:C#),用于传递对象本身,而在调用的时候则不 必显式传递,系统会自动传递. 今天我们介绍的主角是su ...
- python super()函数
super()函数是用来调用父类(超类)的一个方法 super()的语法: python 2 的用法: super(Class, self).xxx # class是子类的名称 class A(ob ...
- Python ---- super()使用
Python ---- super() 我们经常在类的继承当中使用super(), 来调用父类中的方法.例如下面: 1 2 3 4 5 6 7 8 9 10 11 12 13 class A: ...
- Python高级用法总结
Python很棒,它有很多高级用法值得细细思索,学习使用.本文将根据日常使用,总结介绍Python的一组高级特性,包括:列表推导式.迭代器和生成器.装饰器. 列表推导(list comprehensi ...
- python argparse用法总结
转:python argparse用法总结 1. argparse介绍 argparse是python的一个命令行解析包,非常适合用来编写可读性非常好的程序. 2. 基本用法 prog.py是我在li ...
- Anaconda下载及安装及查看安装的Python库用法
Anaconda下载及安装及查看安装的Python库用法 Anaconda 是一个用于科学计算的 Python 发行版,提供了包管理与环境管理的功能.Anaconda 利用 conda 来进行 pac ...
- python enumerate用法总结【转】
enumerate()说明 enumerate()是python的内置函数 enumerate在字典上是枚举.列举的意思 对于一个可迭代的(iterable)/可遍历的对象(如列表.字符串),enum ...
- this和super用法
1. this能分清混淆,形参名与当前对象的某个成员有相同的名字,需要明确使用this关键字来指明你要使用某个成员,使用方法是“this.成员名”. 一般以this.形参数名=形参名,代表送进来赋值的 ...
随机推荐
- JavaScript中undefined和not defined 的区别
参考:某个大佬的博客 以下原创: <script type="text/javascript"> console.log(a); a = 100; </scrip ...
- XML -- XML基础知识
1.XML的结构 XML的结构采用的是树结构,树结构就是一种一对多的关系结构. XML必定有一个根节点.因为,在解析这个XML时,需要有个起始位置进行遍历解析,这个跟节点就是起始位置. 2.XML的元 ...
- Mockito 2 参数匹配器
Mockito 通过使用 equals() 这种自然的 Java 样式来校验参数值.有时候,当需要有其他一些灵活性的时候,你可能会要求使用参数匹配(argument matchers). 请参考下面的 ...
- noi.ac #531 神树和物品
题目链接:戳我 决策单调性 (蒟蒻终于会写决策单调性啦!考试全场切这题就我不会啊嘤) (证明?不会啊,自己打表看QAQ) 44pts \(O(n^2)\)代码: #include<iostrea ...
- Quartz.NET 作业调度(一):Test
Quartz.NET 是一个开源的作业调度框架,是 Java 作业调度框架 Quartz 的.NET 版本,对于周期性的任务,其作业和触发器的结合,极大的简化了代码的编写,大多时候我们只需要关注作业本 ...
- Xshell远程连接的具体操作和Xshell多会话设置小技巧
前几天给大家分享了Xshell的安装教程,今天给大家分享如何在Xshell中进行远程连接,并且分享一下如何设置一条命令可以发送多个终端,这里以Xshell6为例进行说明,具体的教程如下. 1.依次点击 ...
- kali安装与配置
闲来没事,把kali虚拟机重新装到了电脑上,记录下步骤 1.在kali官网(https://www.kali.org/downloads/)直接下载的.ova虚拟机,因为之前从官网下载的iso文件不知 ...
- LeetCode 143. 重排链表(Reorder List)
题目描述 给定一个单链表 L:L0→L1→…→Ln-1→Ln , 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→… 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换. ...
- php laravel左连接leftJoin多条where语句
通常情况下我们在做leftjoin连接时需要对不止一个条件进行进行匹配,这时候就需要使用闭包方式,如下: leftjoin('db', function ($join) {···}); leftjoi ...
- Java-GC 垃圾收集器(HotSpot)
垃圾收集器为垃圾收集算法的具体实现,是执行垃圾收集算法的,是守护线程. HotSpot 虚拟机采用分代收集(JVM 规范并未对堆区进行划分),将堆分为年轻代和老年代,垃圾收集器也按照这样区分.不过已有 ...