现在回到 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) 其输出如下:

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)

尝试创建 MyClass 实例将会引发 TypeError,原因是与父类的__init__()签名不 匹配,如下所示:

MyClass(10) my base Traceback (most recent call last): File "", line 1, in ", 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 不过这是一种很糟糕的解决方法,因为它使得所有构造函数都接受任何类型的参数。 这会导致代码变得脆弱,因为任何参数都可以传入并通过。另一种解决方法是在 MyClass 中显式地使用特定类的__init()调用,但这又会导致第一种错误。

最佳实践

为了避免前面提到的所有问题,在 Python 在这个领域取得进展之前,我们需要考虑以

下几点。

• 应该避免多重继承:可以采用第 14 章介绍的一些设计模式来代替它。

• super 的使用必须一致:在类的层次结构中,要么全部用 super,要么全不用。

混用 super 和传统调用是一种混乱的做法。人们往往会避免使用 super,这样代

码会更清晰。

• 如果代码的使用范围包括 Python 2,在 Python 3 中也应该显式地继承自 object:

在 Python 2 中,没有指定任何祖先的类被认为是旧式类。在 Python 2 中应避免混

用旧式类和新式类。

• 调用父类时必须查看类的层次结构:为了避免出现任何问题,每次调用父类时,必

须快速查看有关的 MRO(使用__mro__)。

高级属性访问模式 许多 C++和 Java 程序员第一次学习 Python 时,他们会对 Python 没有 private 关键 字感到惊讶。与之最接近的概念是名称修饰(name mangling)。每当在一个属性前面加上 __前缀,解释器就会立刻将其重命名: class MyClass: __secret_value = 1 利用原始名称访问__secret_value 属性,将会引发 AttributeError 异常:

instance_ of = MyClass() instance _of. _secret _value Traceback (most recent call last): File "", line 1, in AttributeError: 'MyClass' object has no attribute ' _secret value' dir(MyClass) [' _MyClass _secret _value', ' __class __', ' __delattr __', ' __dict __', ' __ dir __', ' __doc __', ' __eq __', ' __format __', ' __ge __', ' __getattribute __', ' __gt __', ' __hash __', ' __init __', ' __le __', ' __lt __', ' __module __', ' __ne __', ' __new __', ' __reduce __', ' _reduce _ex __', ' __repr __', ' __ setattr __', ' __sizeof __', ' __str __', ' __subclasshook __', ' __weakref _'] instance of. _MyClass _secret _value 1

Python 提供这一特性是为了避免继承中的名称冲突,因为属性被重命名为以类名为前

缀的名称。这并不是真正的锁定(real lock),因为可以通过其组合名称来访问该属性。这

一特性可用于保护某些属性的访问,但在实践中,永远不应使用__。如果一个属性不是公

有的,约定使用_前缀。这不会调用任何名称修饰的算法,而只是说明这个属性是该类的私

有元素,这是流行的写法。

Python 中还有其他可用的机制来构建类的公有部分和私有代码。应该使用描述符和

property 这些 OOP 设计的关键特性来设计一个清晰的 API。