你会使用super()吗?你确定你了解它吗?
我们经常在类的继承当中使用super(), 来调用父类中的方法。例如下面:
class A:
def func(self):
print('OldBoy') class B(A):
def func(self):
super().func()
print('LuffyCity') A().func()
B().func()
输出的结果为:
OldBoy
OldBoy
LuffyCity
A实例化的对象调用了func方法,打印输出了 Oldboy;
B实例化的对象调用了自己的func方法,先调用了父类的方法打印输出了 OldBoy ,再打印输出 LuffyCity 。
这样是Python3的写法,今天咱们也只讨论Python3中的super。
如果不使用super的话,想得到相同的输出截个,还可以这样写B的类:
class B(A):
def func(self):
A.func(self)
print('LuffyCity')
这样能实现相同的效果,只不过传了一个self参数。那为什么还要使用super()呢?
那我看看有这样的一个继承关系的类(钻石继承):
Base
/ \
/ \
A B
\ /
\ /
C
代码是这样的:
class Base:
def __init__(self):
print('Base.__init__') class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__') class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__') class C(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__') C()
输出的结果是:
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__init__
每个子类都调用父类的__init__方法,想把所有的初始化操作都做一遍,但是出现了一个问题,Base类的__init__方法被调用了两次,这是多余的操作,也是不合理的。
那我们改写成使用super()的写法:
class Base:
def __init__(self):
print('Base.__init__') class A(Base):
def __init__(self):
super().__init__()
print('A.__init__') class B(Base):
def __init__(self):
super().__init__()
print('B.__init__') class C(A, B):
def __init__(self):
super().__init__()
print('C.__init__') C()
输出的结果是:
Base.__init__
B.__init__
A.__init__
C.__init__
这样执行的结果就比较满意,是大多数人想要的结果。那为什么会是这样的结果呢?
那是因为我们每定义一个类的时候,Python都会创建一个MRO列表,用来管理类的继承顺序。
print(C.mro())
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>]
Python通过这个列表从左到右,查找继承的信息。Python3中的类都是新式类,都有这个mro属性,能看出来是广度优先的查找原则。经典类就没有mro属性,但它的查找原则是深度优先。
那我回到super的问题上来,让我们先看看super的官方定义。
super([type[, object-or-type]])
返回一个代理对象,该对象将方法调用委托给类的父类或兄弟类。这对于访问类中已重写的继承方法非常有用。搜索顺序与getattr()使用的搜索顺序相同,只是类型本身被跳过。
类的__mro__属性列出了getattr()和super()使用的方法解析搜索顺序。属性是动态的,可以在继承层次结构更新时进行更改。
看到官方的解释就可以很清楚的明白,super是一个类,实例化之后得到的是一个代理的对象,而不是得到了父类,并且我们使用这个代理对象来调用父类或者兄弟类的方法。
那我们再看看super的使用方法:
super() -> same as super(__class__, <first argument>)
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)
super至少需要一个参数,并且类型需要是类。
不传参数的会报错。只传一个参数的话是一个不绑定的对象,不绑定的话也就没什么用了。
print(super(C))
print(super())
输出结果:
RuntimeError: super(): no arguments
<super: <class 'C'>, NULL>
在定义类当中可以不写参数,Python会自动根据情况将两个参数传递给super。
class C(A, B):
def __init__(self):
print(super())
super().__init__()
print('C.__init__') C()
输出结果:
<super: <class 'C'>, <C object>>
Base.__init__
B.__init__
A.__init__
C.__init__
所以我们在类中使用super的时候参数是可以省略的。
第二种用法, super(type, obj) 传递一个类和对象,得到的是一个绑定的super对象。这还需要obj是type的实例,可以不是直接的实例,是子类的实例也行。
a = A()
print(super(A, a))
print(super(Base, a))
输出结果:
Base.__init__
A.__init__
<super: <class 'A'>, <A object>>
<super: <class 'Base'>, <A object>>
第三种用法, super(type, type2)传递两个类,得到的也是一个绑定的super对象。这需要type2是type的子类。
print(super(Base, A))
print(super(Base, B))
print(super(Base, C))
输出结果:
<super: <class 'Base'>, <A object>>
<super: <class 'Base'>, <B object>>
<super: <class 'Base'>, <C object>>
接下来我们就该说说查找顺序了,两个参数,是按照那个参数去计算MRO呢?
我们将C类中的super的参数填写上,并且实例化,看看输出的结果。
class C(A, B):
def __init__(self):
super(C, self).__init__()
print('C.__init__')
输出结果:
Base.__init__
B.__init__
A.__init__
C.__init__
看结果和之前super没填参数的结果是一样的。
那我们将super的第一个参数改为A:
class C(A, B):
def __init__(self):
super(A, self).__init__()
print('C.__init__')
输出结果:
Base.__init__
B.__init__
C.__init__
咦!?那A.__init__怎么跑丢了呢?多出来了B.__init__呢?
这是应为Python是按照第二个参数来计算MRO,这次的参数是self,也就是C的MRO。在这个顺序中跳过一个参数(A)找后面一个类(B),执行他的方法。
知道这个后,输出的结果就可以理解了。 super(A, self).__init__() 没有执行Base的方法,而是执行了B的方法。
那我们接下来说说 super(type, obj) 和 super(type, type2)的区别。
代码如下:
class Base:
def func(self):
return 'from Base' class A(Base):
def func(self):
return 'from A' class B(Base):
def func(self):
return 'from B' class C(A, B):
def func(self):
return 'from C' c_obj = C() print(super(C, C))
print(super(C, c_obj))
输出结果:
<super: <class 'C'>, <C object>>
<super: <class 'C'>, <C object>>
两次的打印结果一模一样,verygood。那他们的方法是否是一样的呢?测试一下。
print(super(C, C).func is super(C, c_obj).func)
print(super(C, C).func == super(C, c_obj).func)
输出结果:
False
False
他俩的方法既不是指向同一个,值还不相等。是不是搞错了呢?再试试下面的看看。
c1 = super(C, C)
c2 = super(C, C)
print(c1 is c2)
print(c1 == c2)
print(c1.func is c2.func)
print(c1.func == c2.func)
输出结果:
False
False
True
True
c1和c2不是一个对象,但是他们的方法却是相同的。
那 super(C, C).func 和 super(C, c_obj).func 的确是不同的。那打印出来看看有什么区别:
print(super(C, C).func)
print(super(C, c_obj).func)
输出结果:
<function A.func at 0x0000000009F4D6A8>
<bound method A.func of <__main__.C object at 0x00000000022A94E0>>
super的第二个参数传递的是类,得到的是函数。
super的第二个参数传递的是对象,得到的是绑定方法。
函数和绑定方法的区别就不再赘述了,在这里想得到一样的结果,只需要给函数传递一个参数,而绑定方法则不需要传递额外的参数了。
print(super(C, C).func(c_obj))
print(super(C, c_obj).func())
输出结果:
from A
from A
那我现在总结一下:
- super()使用的时候需要传递两个参数,在类中可以省略不写,我们使用super()来找父类或者兄弟类的方法;
- super()是根据第二个参数来计算MRO,根据顺序查找第一个参数类后的方法。
- super()第二个参数是类,得到的方法是函数,使用时要传self参数。第二个参数是对象,得到的是绑定方法,不需要再传self参数。
给使用super()的一些建议:
- super()调用的方法要存在;
- 传递参数的时候,尽量使用*args 与**kwargs;
- 父类中的一些特性,比如【】、重写了__getattr__,super对象是不能使用的。
- super()第二个参数传的是类的时候,建议调用父类的类方法和静态方法。
你会使用super()吗?你确定你了解它吗?的更多相关文章
- 子类继承父类时JVM报出Error:Implicit super constructor People() is undefined for default constructor. Must define an explicit constructor
当子类继承父类的时候,若父类没有定义带参的构造方法,则子类可以继承父类的默认构造方法 当父类中定义了带参的构造方法,子类必须显式的调用父类的构造方法 若此时,子类还想调用父类的默认构造方法,必须在父类 ...
- [LeetCode] Super Ugly Number 超级丑陋数
Write a program to find the nth super ugly number. Super ugly numbers are positive numbers whose all ...
- Maven Super POM
Maven super POM defines some properties. Three ways to find it ${M2_HOME}/lib/maven-model-builder-3. ...
- java基础 super 子类调用父类
如果希望在子类中,去调用父类的构造方法,要求在子类的构造函数调用 example如下: package test; /* * 如果希望在子类中,去调用父类的构造方法,要求在子类的构造函数调用 * */ ...
- Python类中super()和__init__()的关系
Python类中super()和__init__()的关系 1.单继承时super()和__init__()实现的功能是类似的 class Base(object): def __init__(sel ...
- java方法重载(overload)、重写(override);this、super关键简介
一.方法重载: 条件:必须在一个类中,方法名称相同,参数列表不同(包括:数据类型.顺序.个数),典型案例构 造方重载. 注意:与返回值无关 二.方法重写: 条件: (1)继承某个类或实现某接口 (2 ...
- Java super关键字活用
在实际开发中我们要自定义组件,就需要继承自某个组件类,如果我们自定义的这个组件类也需要像被继承的这个组件类一样,拥有丰富的构造方法. 关键字super的作用就更加显得尤为重要了,你可以在堆砌自己自定义 ...
- 深入super,看Python如何解决钻石继承难题 【转】
原文地址 http://www.cnblogs.com/testview/p/4651198.html 1. Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通 ...
- 关于[super dealloc]
销毁一个对象时,需要重写系统的dealloc方法来释放当前类所拥有的对象,在dealloc方法中需要先释放当前类中所有的对象,然后再调用[super dealloc]释放父类中所拥有的对象.如先调用[ ...
- ubuntu super daemon设置
super daemon是一个在Linux下面全面管理自己服务设置的东东,他可以接管很多服务的设定,只需要在/etc/xinetd.d/下面放置好自己的配置文件就可以了,那么,具体应该怎么配置呢? ...
随机推荐
- PHP仿LED点阵,读取字库文字,并转化为二进制输出
<?php $xml=simplexml_load_file("zimu.xml");//导入XML文件,从XML文件里知道需要提取的字体的信息 $font_height=$ ...
- JDK7新特性try-with-resources语句
try-with-resources语句是一种声明了一种或多种资源的try语句.资源是指在程序用完了之后必须要关闭的对象.try-with-resources语句保证了每个声明了的资源在语句结束的时候 ...
- 自动更新前加密:Clickonce用法
一.加密dll 新建一个windows form application: static void Main(string[] args) { Process. ...
- Python Socket函数及说明
- root和alias的区别
先来看看官方说明: root 的用法: location /request_path/image/ { root /local_path/; } 当客户端请求 /request_path/image/ ...
- js判断是否为空
http://dushanggaolou.iteye.com/blog/1293803 1.<input type="hidden" id="key" n ...
- #include<iostream>与#include<iostream.h>以及#inclue<string> 和 #include<string.h>的区别
转载于:http://www.cnblogs.com/charley_yang/archive/2010/12/08/1900715.html 1.从功能性的角度来讲,<iostream> ...
- 汉诺塔hanoi
问题描述: 有一个梵塔,塔内有三个座A.B.C,A座上有诺干个盘子,盘子大小不等,大的在下,小的在上(如图). 把这些个盘子从A座移到C座,中间可以借用B座但每次只能允许移动一个盘子,并且在移动过程中 ...
- pthread_detach
http://blog.csdn.net/scanery/article/details/7241890 感谢作者! 近来发现 在线程函数第一行调用 pthread_detach(pthread_ ...
- Scala进阶之路-高级数据类型之数组的使用
Scala进阶之路-高级数据类型之数组的使用 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数组的初始化方式 1>.长度不可变数组Array 注意:顾名思义,长度不可变数 ...