python中的super到底是什么

前言

我们可能在一些代码中见过这样的写法,如下,在一个类的初始化方法中使用super().__init__() 。这样做的目的是什么呢?或者说这个 super 到底是什么?

class MyThread(threading.Thread):
    def __init__(self, func, args=()):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args

什么是 init

在解释 super 之前,我们先看看__init__是什么。
init 字面意思就是初始化,是一个 python 类的构造方法,也叫类的初始化方法。作用是初始化新创建对象的状态。当一个类的对象被创建时,该构造方法会立即并且自动去执行。如下

class Demo:
    def __init__(self):
        print("hello")


if __name__ == '__main__':
    Demo()

输出

hello

Process finished with exit code 0

Demo() 这种写法其实把创建实例和初始化实例这两步简化了。它过程其实就是:

d = Demo.__new__(Demo)  # 创建一个Demo类的实例
Demo.__init__(d)  # 初始化Demo类的实例d

__init__(self) 中的 self 其实就是类的一个实例,而类的实例是通过__new__方法创建出来的。
而在Java中创建一个实例用的是 new 关键字,而在 python 中不需要。

那么在实际使用中,如果我们需要在类对象创建后、实例方法执行前就执行一些操作(比如读取配置文件),就可以将其放到初始化方法中。

什么是 super

从字面意思来讲,super 是超、超级的意思。而在面向对象关系中,我们又有着继承这一特性,就是说一个类可以继承另一个类的所有属性及方法,那么把被继承的类就叫做父类,也叫超类或者基类,那这个 super 一定是与父类有关系了。

以下代码体现了继承关系的实现,Person 是 Man 的父类或者说基类,超类。

个人觉得叫基类其实更符合理解一些,因为继承之后子类(也有叫派生类)实际上更强大了。

class Person:
    def __init__(self, name):
        self.name = name
        print(f"{self.__class__.__name__}的name属性为{self.name}")

    def eat(self, food):
        print(f"{self.name} eat {food}")

    def learn(self, language):
        print(f"{self.name} learn {language}")


class Man(Person):
    def __init__(self, age, name):
        super().__init__(name)
        self.age = age

    def have_a_beard(self):
        print(f"{self.name} have a beard, Because he's {self.age} years old.")

可以看到,Man继承了Person,而Man的__init__方法对其父类的__init__进行了重载。并且继承了父类的name属性,而继承父类属性时就需要用到super().__init__()

super() 原理是什么?

为什么要以super().__init__()这样的方式调用?它的结果又是什么?
在 pycharm 中,我们可以使用 CTRL+鼠标左键双击 super 查看它的源码,看看里面有什么参数。

源码

可以看到首先它是一个类,super()就是 super 类的一个实例对象,它的__init__方法中有两个参数type1type2

代码文档的解释:

  • super():相当于super(Man, self)
  • super(type):未绑定的super对象。
  • super(type, obj):绑定的super对象,必须:obj的类型是type。
  • super(type, type2):绑定的super对象,必须:type2是type的子类。

我们在初始化方法里面分别打印它们,验证一下:

class Man(Person):
    def __init__(self, age, name):
        print(super())
        print(super(Man))
        print(super(Man, self))
        print(super(Person, Man))
        super().__init__(name)
        self.age = age


if __name__ == '__main__':
    m = Man(23, "lily")

结果

<super: <class 'Man'>, <Man object>>
<super: <class 'Man'>, NULL>
<super: <class 'Man'>, <Man object>>
<super: <class 'Person'>, <Man object>>

Process finished with exit code 0

super 的执行过程

假设有个使用场景为 super(type, type2).func(*args) ,那么它的执行步骤为:

  • 解析 type2mro 列表,假设为 mro_list;
  • 在 mro_list 中查找处于 type 后的所有类, 假设为 new_mro_list;
  • 然后根据 new_mro_list 里类的顺序依次查找 func,如果找到了,就不往后找了,如果没找到,往后遍历,直到找到为止。
  • 找到后直接type2.func(),执行func里的代码。

什么是 mro?

mro : 方法解析顺序。英文 method resolution order

在这里插入图片描述

在python中我们有两种方法可以查看mro

  • 调用类的 __mro__ 、或 mro() 方法。
  • 调用 type()mro 方法。

试一下:

if __name__ == '__main__':
    m = Man(23, "lily")
    print(Man.__mro__)
    print(Man.mro())
    print(type(m).mro())

结果:

(<class '__main__.Man'>, <class '__main__.Person'>, <class 'object'>)
[<class '__main__.Man'>, <class '__main__.Person'>, <class 'object'>]
[<class '__main__.Man'>, <class '__main__.Person'>, <class 'object'>]

Process finished with exit code 0

可以看到内容是一样的,唯一的区别就是:一个是元组,一个列表。

(<class '__main__.BlackMan'>, <class '__main__.Man'>, <class '__main__.Person'>, <class 'object'>)

python默认所有的类都继承自object类。 在python2中定义类必须写为:class Demo(object),python3中不强制要求了,可以简写为class Demo:

再回到 super 的执行过程。
super(Man, self). 举例来说,简写为 super() 。那么它的执行过程就是:

  • 在 Man 的 mro 列表中查找处于 Man 后的类。即 [Personobject] 。
  • 在 [Personobject] 中依次查找 __init__ 方法,找到Person了,好了,不找了。
  • 用 Man 的实例对象调用 Person__init__ 方法。

我们不妨验证一下,对于一个 super(type, type2) 来说:查找顺序是从type开始的(不包括type):
super(Man, self) 改为 super(Person, self)。即

class Man(Person):
    def __init__(self, age, name):
        # super(Man, self) 改为 super(Person, self)
        super(Person, self).__init__(name)
        self.age = age

运行结果为:

    super(Person, self).__init__(name)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

Process finished with exit code 1

可以看到报错了,它说Object.init() 只接受一个参数(要初始化的实例)。因为 super(Person, self)selfmro 列表中查找的是处于Person后的类。只有 object, 它找到的是 object。而 object的__init__方法只有一个self参数,但是你写的是 super(Person, self).__init__(name) ,传了name参数, 所以会报错。

多重继承

如果是多重继承,那执行情况就有点复杂了,不过还是会以 mro 的顺序去调用
有兴趣可以看这篇文章并亲自跑一下代码: Python super 详解:https://www.runoob.com/w3cnote/python-super-detail-intro.html

super的用途

可以这个作者的文章:https://zhuanlan.zhihu.com/p/415729655

总结

super 是用来解决多重继承问题的,super(type, type2)type2 用来绑定一个类或者实例, type 用来告诉 python 解释器从 type2mro 列表的哪个位置开始查找。

在多重继承下,类之间的关系比较复杂,可能子类有时候需要重写或重载父类的有些方法,但我们并不知道是哪一个类,所以super的存在是为了方便我们调用父类的方法,至于调用哪一个父类的方法,取决于我们给super传的参数。而self与super的区别在于,self代表类的实例,self调用一个方法首先从自己本身查找,super会从父类开始查找。

最后,本人知识尚浅,这篇文章也只是我浅薄的理解,想要更好的理解 super 以及 python 的一些其他特性、原理,需要参考官方文档,更为耐心的学习python源码。

参考

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值