在 python 中使用 super 继承
一、引言
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
就不应该被初始化多次],而且继承的先后顺序是固定统一的,让你的同事一眼就能知道。

对于上述问题,python
使用方法解析顺序
(Method Resolution Order)也就是MRO
,MRO
将类的多继承碾平成一条固定的继承链,[顺序固定
,去重
]。
这样我们在使用时就可以看成一个单继承来使用。
例如上述的菱形继承
,我们查看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
的先后关系去一一初始化,而不是自己创造一种初始化方式。
-
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()
方法。 这个用类C
的MRO
列表就可以完全解释清楚了:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class 'object'>)
>>>
在类C
的MRO
列表中,B
是作为A
的父类排列的,所以类A中使用 super().spam()
起了作用。