我们经常在类的继承当中使用super(), 来调用父类中的方法。例如下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
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的类:

1
2
3
4
class B(A):
    def func(self):
        A.func(self)
        print('LuffyCity')

这样能实现相同的效果,只不过传了一个self参数。那为什么还要使用super()呢?

那我看看有这样的一个继承关系的类(钻石继承):

1
2
3
4
5
6
7
  Base
  /  \
 /    \
A      B
 \    /
  /
   C

代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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列表,用来管理类的继承顺序。

1
2
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至少需要一个参数,并且类型需要是类。

不传参数的会报错。只传一个参数的话是一个不绑定的对象,不绑定的话也就没什么用了。

1
2
print(super(C))
print(super())

输出结果:

RuntimeError: super(): no arguments
<super: <class 'C'>, NULL>

在定义类当中可以不写参数,Python会自动根据情况将两个参数传递给super。

1
2
3
4
5
6
7
8
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的实例,可以不是直接的实例,是子类的实例也行。

1
2
3
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的子类。

1
2
3
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的参数填写上,并且实例化,看看输出的结果。

1
2
3
4
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:

1
2
3
4
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)的区别。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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。那他们的方法是否是一样的呢?测试一下。

1
2
print(super(C, C).func is super(C, c_obj).func)
print(super(C, C).func == super(C, c_obj).func)

输出结果:

False
False

他俩的方法既不是指向同一个,值还不相等。是不是搞错了呢?再试试下面的看看。

1
2
3
4
5
6
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 的确是不同的。那打印出来看看有什么区别:

1
2
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的第二个参数传递的是对象,得到的是绑定方法。

函数和绑定方法的区别就不再赘述了,在这里想得到一样的结果,只需要给函数传递一个参数,而绑定方法则不需要传递额外的参数了。

1
2
print(super(C, C).func(c_obj))
print(super(C, c_obj).func())

输出结果:

from A
from A

那我现在总结一下:

  1. super()使用的时候需要传递两个参数,在类中可以省略不写,我们使用super()来找父类或者兄弟类的方法;
  2. super()是根据第二个参数来计算MRO,根据顺序查找第一个参数类后的方法。
  3. super()第二个参数是类,得到的方法是函数,使用时要传self参数。第二个参数是对象,得到的是绑定方法,不需要再传self参数。

给使用super()的一些建议:

  1. super()调用的方法要存在;
  2. 传递参数的时候,尽量使用*args 与**kwargs;
  3. 父类中的一些特性,比如【】、重写了__getattr__,super对象是不能使用的。
  4. super()第二个参数传的是类的时候,建议调用父类的类方法和静态方法。

类的super的更多相关文章

  1. Python类中super()和__init__()的关系

    Python类中super()和__init__()的关系 1.单继承时super()和__init__()实现的功能是类似的 class Base(object): def __init__(sel ...

  2. 【转】python类中super()和__init__()的区别

    [转]python类中super()和__init__()的区别 单继承时super()和__init__()实现的功能是类似的 class Base(object): def __init__(se ...

  3. 【转】python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法>

    [转]python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法> MRO了解: 对于支持继承的编程语言来说,其方法(属性)可能定义 ...

  4. 类继承-super, 私有变量

    多继承 class A: def ces(self): print('a-ces') class B(A): def ces(self): print('b-ces') class C(A): def ...

  5. python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法>

    MRO了解: 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法所在的位置.而搜索的顺序就是所谓的「方法解析顺序」(M ...

  6. python类、super函数

    #PYTHON语言及其应用学习笔记 1.创建简单的类 class Person(): #python中特殊的对象初始化方法__init__,一个特殊的函数名 #当你在类声明里定义__init__()方 ...

  7. js--class类、super和estends关键词的学习笔记

    前言 JavaScript 语言在ES6中引入了 class 这一个关键字,在学习面试的中,经常会遇到面试官问到谈一下你对 ES6 中class的认识,同时我们的代码中如何去使用这个关键字,使用这个关 ...

  8. python类中super()和__init__()的区别

    class Base(object):     def __init__(self): print 'Base create' class childB(Base): def __init__(sel ...

  9. python类继承的重写和super

    给已经存在的类添加新的行为,继承是非常好的实现方式.但是如果要改变行为呢?比如在Python继承扩展内置类,我们的contact类只允许一个名字和一个邮箱,但是如果要对某些人增加电话号码呢?这里可以通 ...

随机推荐

  1. 应有dataGridView控件

    using System.Data.SqlClient; namespace UseDataGridView { public partial class Form1 : Form { public ...

  2. js实现div的置底

    //-------------置底的div---------------------- <div class="mui-content lv-mrcd"  id=" ...

  3. ubuntu 14.04/16.04/18.04 yum安装 zabbix-agent 教程

    备忘 环境:Ubuntu 14.04 基于官网配置官网链接 ①导入源: ### Ubuntu 18.04 # wget https://repo.zabbix.com/zabbix/3.4/ubunt ...

  4. IIS状态码大全【转】

    对于站长来说,经常分析下网站的IIS日志是有好处的,这样可以随时了解SE蜘蛛是否经常光顾自己的网站,都抓取了哪些页面,被抓取的页面哪些是被正常的,哪些是不正常的.而IIS日志有专门的返回状态码,为了方 ...

  5. js调绝对定位的top

    $("ggg div").each(function () {                this.style.top = (parseFloat(this.style.top ...

  6. thinkphp nginx pathinfo模式支持

    最近一个项目中使用了ThinkPHP做为开发框架,URL上我们使用了PATHINFO模式,但是Nginx默认是不支持PATHINFO的,需要进行手动配置才可以,于是我们按照了以下方法进行了Nginx的 ...

  7. ASP.NET MVC 随想录

    http://www.cnblogs.com/OceanEyes/category/696137.html

  8. Hibernate_day02--课程安排_主键生成策略_对实体类crud操作_实体类对象状态

    Hibernate_day02 上节内容 今天内容 实体类编写规则 Hibernate主键生成策略 实体类操作 对实体类crud操作 添加操作 根据id查询 修改操作 删除操作 实体类对象状态(概念) ...

  9. ios开发之--系统控件显示中文

    虽然一直知道X-code肯定提供有语言本地化的设置地方,但是一直也做个记录,有些时候的汉化,还是需要使用代码去控制,键盘的右下角.navagiton的return使用代码修改,调用系统相机时,也是出现 ...

  10. Cognos组织架构介绍

    Cognos只是一个工具,说到Cognos相信大部分人都知道BI(商业智能,Business Intelligence). Cognos也是属于SOA架构,面向服务的体系结构,是一个组件模型,它将应用 ...