现在回到 super。如果使用了多重继承的层次结构,那么使用 super 是非常危险的, 主要原因在于类的初始化。在 Python 中,基类不会在__init__()中被隐式地调用,所以 需要由开发人员来调用它们。我们来看几个例子。
1.混用 super 与显式类调用
在下面来自 James Knight 网站(http://fuhm.net/super-harmful)的示例中,C 类使用
__init__方法调用它的基类,使得 B 类被调用了两次:
class A:
def __init__(self):
print("A", end=" ") super().__init__()
class B:
def __init__(self):
print("B", end=" ") super().__init__()
class C(A, B):
def __init__(self):
print("C", end=" ") A.__init__(self) B.__init__(self)
其输出如下:
3.2 访问超类中的方法 71
>>> print("MRO:", [x.__name__ for x in C.__mro__]) MRO: ['C', 'A', 'B', 'object']
>>> C()
C A B B <__main__.C object at 0x0000000001217C50>
出现以上这种情况的原因在于,C 的实例调用了 A.__init__(self),因此使得 super(A, self).__init__()调用了 B.__init__()方法。换句话说,super 应该被 用到整个类的层次结构中。问题在于,有时这种层次结构的一部分位于第三方代码中。在 James 的页面上可以找到许多由多重继承引入的层次调用相关的错误。
不幸的是,你无法确定外部包的代码中是否使用了 super()。如果你需要对某个第三 方类进行子类化,最好总是查看其内部代码以及 MRO 中其他类的内部代码。这个过程可 能很枯燥,但作为奖励,你可以了解这个包的代码质量,并对它的实现有了进一步理解。 这样你还可能学习了一些新东西。
2.不同种类的参数
使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名,一个类
怎么能调用其基类的__init__()代码呢?这会导致下列问题:
class CommonBase:
def __init__(self):
print('CommonBase') super().__init__()
class Base1(CommonBase): def __init__(self): print('Base1')
super().__init__()
class Base2(CommonBase): def __init__(self, arg):
print('base2') super().__init__()
class MyClass(Base1 , Base2): def __init__(self, arg):
print('my base') super().__init__(arg)
72 第3章 语法最佳实践—类级别以上
尝试创建 MyClass 实例将会引发 TypeError,原因是与父类的__init__()签名不
匹配,如下所示:
>>> MyClass(10)
my base
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
TypeError: __init__() takes 1 positional argument but 2 were given
一种解决方法是使用*args 和**kwargs 魔法包装的参数和关键字参数,这样即使不 使用它们,所有的构造函数也会传递所有参数,如下所示:
class CommonBase:
def __init__(self, *args, **kwargs):
print('CommonBase') super().__init__()
class Base1(CommonBase):
def __init__(self, *args, **kwargs):
print('Base1') super().__init__(*args, **kwargs)
class Base2(CommonBase):
def __init__(self, *args, **kwargs):
print('base2') super().__init__(*args, **kwargs)
class MyClass(Base1 , Base2): def __init__(self, arg):
print('my base') super().__init__(arg)
利用这种方法,父类的签名将始终匹配:
>>> _ = MyClass(10) my base
Base1
base2
CommonBase