Python 中,由于基类不会在 __init__() 中被隐式地调用,需要程序员显式调用它们。这种情况下,当程序中包含多重继承的类层次结构时,使用 super 是非常危险的,往往会在类的初始化过程中出现问题。
分析如下程序,C 类使用了 __init__() 方法调用它的基类,会造成 B 类被调用了 2 次:
- 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__])
- C()
运行结果为:
出现以上这种情况的原因在于,C 的实例调用 A.__init__(self),使得 super(A,self).__init__() 调用了 B.__init__() 方法。换句话说,super 应该被用到整个类的层次结构中。
但是,有时这种层次结构的一部分位于第三方代码中,我们无法确定外部包的这些代码中是否使用 super(),因此,当需要对某个第三方类进行子类化时,最好查看其内部代码以及 MRO 中其他类的内部代码。
使用 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):
- print("base2")
- super().__init__()
-
- class myClass(base1,base2):
- def __init__(self,arg):
- print("my base")
- super().__init__(arg)
- myClass(10)
运行结果为:
一种解决方法是使用 *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)
运行结果为:
不过,这是一种很糟糕的解决方法,由于任何参数都可以传入,所有构造函数都可以接受任何类型的参数,这会导致代码变得脆弱。另一种解决方法是在 MyClass 中显式地使用特定类的 __init__() 调用,但这无疑会导致第一种错误。
如果想要避免程序中出现以上的这些问题,这里给出几点建议: