用super来调用__init__的方式,设计得当的话,可以使得每个类的__init__恰好被调用一次,supr()方式中,最重要的是__init__参数传递的问题,下面提供了一个比较好的解决方案
解决方案:
在定义自己的__init__()时,参数列表是(self, parms, **kargs),其中parms是自身需要
的位置参数,可以给它们添加默认值, kargs是关键字参数,用于传递给下一层的__init__()
然后在构造器函数体内写super().__init__(kargs),详情见下面的栗子.
理由:
假设B直接继承A,且A的构造需要两个参数x和y,则在B的__init__中,可以直接写super().init(x,y),但是这是有问题的,因为继承结构是可以不断扩展的,这就意味着在另一个新定义的类中,它的mro列表中B的后面可能不是A(虽然B是直接继承A的),而是一个和A没有半毛钱关系的类X。
此时B的__init__中的super().__init__(x,y)这个调用语句就会可能会抛出参数不匹配的异常,并且即使参数恰巧匹配了,则这也不是我们想要的结果,因为我们的原意是用x和y调用A的构造器,此时是用x和y调用了X的构造器。
这可以说也是super()使用的一个小坑,此时如果非得要用固定的位置参数的话,则只能用经典类的调用父类构造器的方法了,就是直接用类名调用,此处是,A.__init__(self,x,y)
所以,由于super()的动态性,必须用**关键字参数的方式,此时只要关键字实参本身没有问题,无论super().__init__(**kargs)调用的是哪个类的构造器,参数都会正确传递下去.
注意事项:
例子:
# 代码基于python3.6
"""下图中,两根斜线是指往这个方向数的第二个类,O是object
O
|
G
| \ \
D E F
|/\\/
B C
| /
A
mro=ABCDEFGO
"""
class G:
def __init__(self, g, **kargs): # g是本构造器自身需要的参数,用位置参数将指提取出来,而kargs则是剩余的关键字参
print("enter __init__ G") # 数,用于传递给下一层的__init__()
self.g = g
super(G, self).__init__(**kargs) # 为了和python2的新式类兼容,手动把super的两个参数填上
class D(G):
def __init__(self, d, **kargs):
print("enter __init__ D")
self.d = d
super(D, self).__init__(**kargs) # 调用__init__时,只写**kargs,因为super返回的偏函数自动填充了self参数
class E(G):
def __init__(self, e=100, **kargs): # 给 本类初始化所需要的位置参数 一个默认值,使得传参时可以省掉这个参数
print("enter __init__ E")
self.e = e
super(E, self).__init__(**kargs)
class F(G):
def __init__(self, f=None, **kargs): # 即使这个参数没有默认值,最好也给它一个默认值None,除非这个参数是必须要给出的
print("enter __init__ F")
self.f = f
super(F, self).__init__(**kargs)
class B(D,E):
def __init__(self, b, **kargs):
print("enter __init__ B")
self.b = b
super(B, self).__init__(**kargs)
class C(D,F):
def __init__(self, c, **kargs):
print("enter __init__ C")
self.c = c
super(C, self).__init__(**kargs)
class A(B,C):
def __init__(self, a, **kargs):
print("enter __init__ A")
self.a = a
super(A, self).__init__(**kargs)
def play(self):
print("enter play A")
print(self.__dict__)
aa = A(a=1,b=2,c=3,d=4,e=5,f=6,g=7) # 或者A(1,b=2,...)也可以,但是A(1,2,c=3,..)不可以
aa.play() # 也就是说位置参数可以用位置参数或者关键字参数传递,但是**关键字参数不能用位置参数传递
bb = A(a=1,b=2,c=3,d=4,g=5)
bb.play() #out: {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 100, 'f': None, 'g': 5}