别摸我
别摸我
文章目录
  1. 调用父类方法
    1. 问题
    2. 解决方案
    3. 讨论

调用父类方法

调用父类方法

问题

你想在子类中调用父类的某个已经被覆盖的方法。

解决方案

为了调用父类(超类)的一个方法,可以使用的 super() 函数,比如:

1
2
3
4
5
6
7
8
class A:
def spam(self):
print('A.spam')

class B(A):
def spam(self):
print('B.spam')
super().spam() # Call parent spam()

super() 函数的一个常见用法是在 __init__() 方法中确保父类被正确的初始化了:

1
2
3
4
5
6
7
8
class A:
def __init__(self):
self.x = 0

class B(A):
def __init__(self):
super().__init__()
self.y = 1

super() 的另外一个常见用法出现在覆盖Python特殊方法的代码中,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Proxy:
def __init__(self, obj):
self._obj = obj

# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)

# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)

在上面代码中,__setattr__() 的实现包含一个名字检查。 如果某个属性名以下划线(_)开头,就通过 super() 调用原始的 __setattr__() , 否则的话就委派给内部的代理对象 self._obj 去处理。 这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作。

讨论

我们来看下面调用父类的一个方法:

1
2
3
4
5
6
7
8
class Base:
def __init__(self):
print('Base.__init__')

class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')

尽管对于大部分代码而言这么做并没有什么问题,但是在涉及更复杂的多继承代码中可能会导致很奇怪的问题发生,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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__()被调用了两次

1
2
3
4
5
6
7
>>> c = C()
Base.__init__()
A.__init__()
Base.__init__()
B.__init__()
C.__init__()
>>>

将代码换成 super()就不一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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__')

运行这个版本之后,你会发现每个 __init__只运行了一次。

1
2
3
4
5
6
>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>>

为了弄清它的原理,我们需要花点时间解释下Python是如何实现继承的。 对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。例如:

1
2
3
4
>>>C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>

为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个MRO列表的构造是通过一个C3线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

当你使用 super() 函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。 这也是为什么在第二个例子中你不会调用两次 Base.__init__() 的原因。

super() 有个令人吃惊的地方是它并不一定去查找某个类在MRO中下一个直接父类, 你甚至可以在一个没有直接父类的类中使用它。例如,考虑如下这个类:

1
2
3
4
class A:
def spam(self):
print('A.spam')
super().spam()

如果你试着直接使用这个类就会出错:

1
2
3
4
5
6
7
8
>>> 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'
>>>

但是,如果你使用多继承的话看看会发生什么:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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列表就可以完全解释清楚了:

1
2
3
4
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class 'object'>)
>>>

在定义混入类的时候这样使用 super() 是很普遍的。

然而,由于 super() 可能会调用不是你想要的方法,你应该遵循一些通用原则。 首先,确保在继承体系中所有相同名字的方法拥有可兼容的参数签名(比如相同的参数个数和参数名称)。 这样可以确保 super() 调用一个非直接父类方法时不会出错。 其次,最好确保最顶层的类提供了这个方法的实现,这样的话在MRO上面的查找链肯定可以找到某个确定的方法。

支持一下
扫一扫,支持heaven
  • 微信扫一扫
  • 支付宝扫一扫