Python 高手编程系列三千四百二十五:使用 super 易犯的错误

Python 高手编程系列三千四百二十五:使用 super 易犯的错误 现在回到 super。如果使用了多重继承的层次结构那么使用 super 是非常危险的主要原因在于类的初始化。在 Python 中基类不会在__init__()中被隐式地调用所以需要由开发人员来调用它们。我们来看几个例子。混用 super 与显式类调用在下面来自 James Knight 网站http://fuhm.net/super-harmful的示例中C 类使用__init__方法调用它的基类使得 B 类被调用了两次class A:definit(self):print(“A”, end “)super().init()class B:definit(self):print(“B”, end” “)super().init()class C(A, B):definit(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 中其他类的内部代码。这个过程可能很枯燥但作为奖励你可以了解这个包的代码质量并对它的实现有了进一步理解。这样你还可能学习了一些新东西。不同种类的参数使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名一个类怎么能调用其基类的__init()代码呢这会导致下列问题class CommonBase:definit(self):print(‘CommonBase’)super().init()class Base1(CommonBase):definit(self):print(‘Base1’)super().init()class Base2(CommonBase):definit(self, arg):print(‘base2’)super().init()class MyClass(Base1 , Base2):definit(self, arg):print(‘my base’)super().init(arg)尝试创建 MyClass 实例将会引发 TypeError原因是与父类的__init()签名不匹配如下所示MyClass(10)my baseTraceback (most recent call last):File “”, line 1, inFile “”, line 4, in __init __TypeError: __init() takes 1 positional argument but 2 were given一种解决方法是使用*args 和**kwargs 魔法包装的参数和关键字参数这样即使不使用它们所有的构造函数也会传递所有参数如下所示class CommonBase:definit(self, *args, **kwargs):print(‘CommonBase’)super().init()class Base1(CommonBase):definit(self, *args, **kwargs):print(‘Base1’)super().init(*args, **kwargs)class Base2(CommonBase):definit(self, *args, **kwargs):print(‘base2’)super().init(*args, **kwargs)class MyClass(Base1 , Base2):definit(self, arg):print(‘my base’)super().init(arg)利用这种方法父类的签名将始终匹配_ _ MyClass(10)my baseBase1base2CommonBase不过这是一种很糟糕的解决方法因为它使得所有构造函数都接受任何类型的参数。这会导致代码变得脆弱因为任何参数都可以传入并通过。另一种解决方法是在 MyClass中显式地使用特定类的__init()调用但这又会导致第一种错误。