提示:本文是阅读书籍《Python 高级编程》的一些疑问梳理结果,用到了其中的某些示例
编程语言中的一些功能都是为了解决某些问题或者提升编码性能而设计的,如果我们能了解这些功能解决背后解决的问题,那么对于我们的使用是很有帮助的,同时也能避免犯一些错误。
代码如下(示例):
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
为什么B会被调用两次?
出现以上这种情况的原因在于,C的实例调用了A.init(self),因此使得super(A, self).init()调用了B.init()方法。换
句话说,super应该被用到整个类的层次结构中。(普通程序员的我完全没有看懂,还是不理解)
可能对supper的理解有误?查了很久的资料:
super就是用来获取父类并用来调用父类方法的,这样说法其实是不对的,使用supper获取的不是父类,而是MRO列表中的下一个类,所谓MRO列表即方法解析顺序(Method Resolution Order)列表,它代表着类继承的顺序,我们可以使用以下几种获得某个类的MRO列表:
C.mro()
C.__mro__
c.__class__.__mro__
【参考:python中super()函数的理解与基本使用】
从C的MRO列表中可以看出,C的实例调用了A.init(self),因此使得super(A, self).init()调用了B.init()方法,因为B在A的后面。
使用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__()签名不匹配,三个类的函数签名如下所示:
Base1 OrderedDict([(‘self’,
Base2 OrderedDict([(‘self’,
CommonBase OrderedDict([(‘self’,
怎么修改了:提供了四种解决方式:
a.所有的类__init__参数都是一样的
class CommonBase:
def __init__(self,arg):
print('CommonBase')
super().__init__()
class Base1(CommonBase):
def __init__(self, arg):
print('Base1')
super().__init__(arg)
class Base2(CommonBase):
def __init__(self, arg):
print('base2')
super().__init__(arg)
class MyClass(Base1 , Base2):
def __init__(self, arg):
print('my base')
print(arg)
super().__init__(arg)
b. 修改继承顺序为Base2, Base1
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( Base2, Base1 ):
def __init__(self, arg):
print('my base')
super().__init__(arg)
c.书中的解决方式
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)
一种解决方法是使用*args和**kwargs魔法包装的参数和关键字参数,这样即使不使用它们,所有的构造函数也会传递所有参数。安全性不高。
d.显示指定调用
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')
print(arg)
Base2.__init__(self, arg)
super()不仅仅是继承这么简单,更多的是和MRO相关,如果遇到解决不了的问题,可以从MRO方向思考一下。书中给了一些建议:
应该避免多重继承:可以采用第14章介绍的一些设计模式来代替它。
super的使用必须一致:在类的层次结构中,要么全部用super,要么全不用。混用super和传统调用是一种混乱的做法。人们往往会避免使用super,这样代码会更清晰。
**如果代码的使用范围包括Python 2,在Python 3中也应该显式地继承自object:**在Python 2中,没有指定任何祖先的类被认为是旧式类。在
Python 2中应避免混用旧式类和新式类。
调用父类时必须查看类的层次结构:为了避免出现任何问题,每次调用父类时,必须快速查看有关的MRO(使用__mro__)。