[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.形参数名=形参名,代表送进来赋值的 ...
随机推荐
- keras中常用的初始化器
keras中常用的初始化器有恒值初始化器.正态分布初始化器.均匀分布初始化器 恒值初始化器: keras.initializers.Zeros() keras.initializers.Ones() ...
- 油猴Tampermonkey离线安装流程(附文件)
1.下载插件插件包,然后解压(解压到你想放插件的位置,其实任意位置都可以,记住解压的位置) 链接:https://pan.baidu.com/s/1aanhsb6ZlapnzBeBRtp3Hg 提取码 ...
- luogu 3441 [POI2006]MET-Subway 拓扑排序+思维
Description 给出一棵N个结点的树,选择L条路径,覆盖这些路径上的结点,使得被覆盖到的结点数最多. Input 第一行两个正整数N.L(2 <= N <= 1,000,000, ...
- poj 2718 Smallest Difference(暴力搜索+STL+DFS)
Smallest Difference Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 6493 Accepted: 17 ...
- codevs3269 混合背包 x
3269 混合背包 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 背包体积为V ,给出N个物品,每个物品占用体积为 ...
- Js模块化开发--seajs和gruntJs
1.Seajs库 解决开发中的冲突依赖等问题,提供代码可维护性. SeaJS 是由玉伯开发的一个遵循 CommonJS 规范的模块加载框架,可用来轻松愉悦地加载任意 JavaScript 模块和css ...
- Vue_(基础)Vue中的事件
Vue.js中文文档 传送门 Vue@事件绑定 v-show:通过切换元素的display CSS属性实现显示隐藏: v-if:根据表达式的真假实现显示隐藏,如果隐藏,它绑定的元素都会销毁,显示的时候 ...
- 线程系列2--Java线程的互斥技术
java的多线程互斥主要通过synchronized关键字实现.一个线程就是一个执行线索,多个线程可理解为多个执行线索.进程有独立的内存空间,而进程中的线程则是共享数据对象资源.这样当多个执行线索在C ...
- Function和Object 应该知道的
javascript有5种基础的内建对象(Fundamental Objects),Object.Function.Error.Symbol.Boolean,而Object/Function尤为特殊, ...
- 构建 JVM(HotSpot) 源码调试环境(OpenJDK8)
原本想在 Windows 下编译调试,但过程中遇到了诸多错误(老是报路径错误...),最后只好放弃. 此次记录调试的方法为 CentOS7 上编译,Windows 上使用 Clion 远程调试(也可直 ...