[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.形参数名=形参名,代表送进来赋值的 ...
随机推荐
- AOP初步
一刀切的AOP基础 软件开发的目的,最终是为了解决各种需求,包括业务和系统的,使用OOP可以对业务需求等普通关注点进行很好的抽象和封装,并且使之模块化. 但OOP却无法解决类似于日志.安全.事务等系统 ...
- Linux下nc或scp命令来实现文件传输
很实用的小技巧, 可以使用nc或者是scp nc命令,转载自:https://www.cnblogs.com/xuybin/archive/2013/09/27/3343098.html 发送端:ca ...
- BZOJ 1778: [Usaco2010 Hol]Dotp 驱逐猪猡 概率与期望+高斯消元
这个还挺友好的,自己相对轻松能想出来~令 $f[i]$ 表示起点到点 $i$ 的期望次数,则 $ans[i]=f[i]\times \frac{p}{q}$ #include <cmath> ...
- Linux环境下软件安装
下载——解压缩: 把得到的目录放到一个不碍事的目录,我们可以设置隐藏目录: 查看可执行程序: 如何变成系统命令? PATH:一个可执行程序只要放到这些目录中任何一个就可以,就可以变成系统识别的命令,当 ...
- 【CUDA 基础】3.5 展开循环
title: [CUDA 基础]3.5 展开循环 categories: - CUDA - Freshman tags: - 展开归约 - 归约 - 模板函数 toc: true date: 2018 ...
- 火焰图分析CPU性能问题
1.找出应用程序或内核消耗CPU的PID 2.执行perf record 命令,记录该PID的行为 perf record -a -g -p 14851 -- sleep 30 --30秒后退出 3. ...
- jmeter下载安装
jmeter运行依靠java环境 一.根据jmeter版本不同要求java环境则不同 jmeter官网下载地址:http://jmeter.apache.org/download_jmeter.cgi ...
- LeetCode 86. 分隔链表(Partition List)
题目描述 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的初始相对位置. 示例: 输入: head = 1-> ...
- 【2】PRD文档介绍
首先,我想说,题主是一个不严肃的人(严肃脸),所以每次干个啥事之前我都喜欢唠唠嗑,说说废话,沟通沟通感情,曾经以为自己将会成为一个幻想中的产品经理那般大展身手,作为非计算机专业出身的应届生,后来才发现 ...
- JAVA记事本的图形用户界面应用程序含加密
JAVA记事本的图形用户界面应用程序 加密 题目简介: 整体分析: 实验代码: import java.awt.EventQueue; import java.awt.event.ActionEven ...