python __init__()构造器中的super()使用用法

用super写构造器

    用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)调用的是哪个类的构造器,参数都会正确传递下去.

注意事项:

  1. 用**关键字传参的方式能解决问题,但是它也要求调用构造器时,必须用关键字参数传递,并且必须正确的传递,此时可以在定义构造器时,给每个参数加上一个默认值,这样的话,关键字传参时即使某些参数漏了,也可以正常调用,否则必须把继承结构中的所有参数的名字以及值都写对才行.
  2. 每个构造器中都必须用super()调用__init__(),不然链条会断裂,导致后面的类没有进行初始化了

例子:

# 代码基于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}

你可能感兴趣的:(python)