在 python 中使用 super 继承

2020-04-22  本文已影响0人  eeert2

一、引言

python语言的三大特性:封装继承多态

使用继承可以提升代码的重用率。

举个栗子:
使用django的过程中,重写模型的.save().delete()方法是很常见的。

from django.db import models


class Image(models.Model):
    name = models.CharField('图片名称', max_length=32)
    path = models.CharField('图片的文件存储地址', max_length=64)
    size = models.IntegerField('图片的大小')

    def delete(self, using=None, keep_parents=False):
        # 从文件存储中删除图片
        ...
        
        # 调用父类的删除方法
        return super().delete(using=None, keep_parents=False)

二、为什么在python中使用super

在继承中,调用父类的方法有两种:直接通过父类名称调用通过super()调用,由于python的多继承设计,使用第一种方式在特殊的情况下可能会引起意想不到的问题。

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__')

如果你运行这段代码就会发现 Base.__init__() 被调用两次,如下所示:

>>> c = C()
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__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__()  # Only one call to super() here
        print('C.__init__')

Base.__init__只执行一次。

>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__

三、谈谈python的多继承与MRO

使用super()关键字为什么可以避免以上问题。因为使用super()后,python会按照MRO去调用父类。

如果你接触过java语言,那对单继承应该很熟悉,java中有super关键字,而python中使用super也能实现同样的效果。这是因为MRO将类的多继承关系碾平成一条固定的单继承链,

python设计为多继承,我们在初始化父类时就有一个先后顺序的问题。例如在上述的例子中:

class C(A,B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)

# 我们也可以这样写:
class C(A,B):
    def __init__(self):
        B.__init__(self)
        A.__init__(self)

我们自己控制初始化就很容易出错,例如下面的多继承关系,我们如何控制去重[例如object就不应该被初始化多次],而且继承的先后顺序是固定统一的,让你的同事一眼就能知道。

截屏2020-04-2209.19.36.png

对于上述问题,python使用方法解析顺序(Method Resolution Order)也就是MROMRO将类的多继承碾平成一条固定的继承链,[顺序固定,去重]。

这样我们在使用时就可以看成一个单继承来使用。

例如上述的菱形继承,我们查看mro链式关系

if __name__ == '__main__':
    print(C.__mro__)
    # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)

打印结果为C -> A -> B -> Base -> object

我们应始终使用super().__init__()按照mro的先后关系去一一初始化,而不是自己创造一种初始化方式。

class A:
    def spam(self):
        print('A.spam')
        super().spam()

直接使用会报错:

>>> a = A()
>>> a.spam()
A.spam
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 4, in spam
AttributeError: 'super' object has no attribute 'spam'
>>>

如果在多继承中就不会报错:

>>> class B:
...     def spam(self):
...         print('B.spam')
...
>>> class C(A,B):
...     pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>

你可以看到在类A中使用 super().spam() 实际上调用的是跟类A毫无关系的类B中的 spam() 方法。 这个用类CMRO列表就可以完全解释清楚了:

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class 'object'>)
>>>

在类CMRO列表中,B是作为A的父类排列的,所以类A中使用 super().spam() 起了作用。

上一篇 下一篇

猜你喜欢

热点阅读