现在回到 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