乐趣区

关于python:类的继承中的super

在 python 中,对于类的继承有很多场景和知识点。明天聚焦在一个场景:有一个父类 A,类中定义了某个问题中的通用属性和办法(即前面的子类都须要用到),在子类 B 中须要继承这些属性和办法,同时增加本人特有的属性和办法,应该如何实现?

在子类中,继承并初始化父类属性的形式有两种:

  1. 显示调用父类的初始化函数,并且对属性进行初始化;
  2. 通过 super() 初始化父类属性;

对于形式 1,代码:

class A:
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def func(self):
        return self.a+self.b

class B(A):
    def __init__(self,x1,x2,x3,x4):
        A.__init__(self,a=x1,b=x2)# 留神必须要传入 self,相当于把 B 的实例传进去
        self.c=x3
        self.d=x4
        self.m=A.func(self)# 同样必须传入 self
        self.n=self.func()
>>>ins=B(1,2,10,20)
>>>print(ins.a,ins.b,ins.c,ins.d)
1 2 10 20
>>>print(ins.m,ins.n)
3 3
>>>print(ins.func())
3     

对于形式 2,代码:

class A:
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def func(self):
        return self.a+self.b

class B(A):
    def __init__(self,x1,x2,x3,x4):
        super().__init__(a=x1,b=x2)# 初始化父类参数,留神不须要 self 了
        self.c=x3
        self.d=x4
        self.m=super().func()#super() 同样能调用父类的办法
        self.n=self.func()# 也能够间接调用父类办法
>>>ins=B(1,2,10,20)
>>>print(ins.a,ins.b,ins.c,ins.d)
1 2 10 20
>>>print(ins.m,ins.n)
3 3
>>>print(ins.func())
3

对于形式 1,显示调用进行初始化,在多重继承的时候可能会呈现反复调用的问题,如:

class A:
    def __init__(self,a,b):
        self.a=a
        self.b=b
        print('AAAAA')
    def func(self):
        return self.a+self.b

class B(A):
    def __init__(self,x1,x2,x3,x4):
        A.__init__(self,a=x1,b=x2)
        self.c=x3
        self.d=x4
        print('BBBBB')

class C(A):
    def __init__(self,m,n,q):
        A.__init__(self,m,n)
        self.q=q
        print('CCCCC')

class D(B,C):
    def __init__(self,a,b,c,d,e,f):
        B.__init__(self,a,b,c,d)
        C.__init__(self,a,b,e)
        self.f=f
        print('DDDDDD')
>>>ins=D(1,2,3,4,5,6)
AAAAA
BBBBB
AAAAA
CCCCC
DDDDDD

能够看到,A 被调用了两次。因为 D 继承了 B 和 C,在初始化 B 的时候,首先会初始化 A,而后初始化 B;在初始化 C 的时候,也会先初始化 A, 再初始化 C;因而 A 就被初始化了两次。

另一个问题是,

而用 super() 尽管能够防止反复调用这个问题,然而在父类均有参数须要初始化时就很麻烦:

class A:
    def __init__(self):
        print('AAAAA')
    def func(self):
        return self.a+self.b

class B(A):
    def __init__(self):
        super().__init__()
        print('BBBBB')

class C(A):
    def __init__(self):
        super().__init__()
        print('CCCCC')

class D(B,C):
    def __init__(self):
        super().__init__()
        print('DDDDDD')
>>>ins=D()
AAAAA
CCCCC
BBBBB
DDDDDD
#可见,的确能够防止反复调用的问题。

然而,如果父类均有参数,那么这个时候问题就很大了,如:

class A:
    def __init__(self,a,b):
        self.a=a
        self.b=b
        print('AAAAA')
    def func(self):
        return self.a+self.b

class B(A):
    def __init__(self,x1,x2,x3,x4):
        super().__init__(x1,x2)
        self.c=x3
        self.d=x4
        print('BBBBB')

class C(A):
    def __init__(self,m,n,q):
        super().__init__(m,n)
        self.q=q
        print('CCCCC')

class D(B,C):
    def __init__(self,a,b,c,d,e,f):
        super().__init__(a,b,c,d)## 这种状况下,因为是依照 MRO 法令来以此对所有父类进行初始化,因而这里无论怎么传参数,都是有可能报错的。因为不同父类的入参不同。依据前面查看的 MRO 程序,调用程序是 D -B-C-A,因而这里初始化的时候 IDE 会提醒输出 4 个参数,因为首先调用的是 B,而 B 的初始化须要 4 个参数。self.f=f
        print('DDDDDD')
        
>>>ins=D(1,2,3,4,5,6)

TypeError: __init__() missing 1 required positional argument: 'q'
        
>>>print(D.__mro__)# 查看 D 的父类调用 MRO 程序
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
#因而。能够看到,首先调用的是 B,初始化胜利;而在初始化 C 的时候报错,C 中的 m,n 都是初始化 A,而 A 曾经在前一步初始化过了。然而因为所有的参数都进了 B,当初初始化 C 的时候缺一个参数 q,因而报错。

对于带参数的多重继承问题,另一个同样的例子能够参考来了解:https://www.pythonf.cn/read/1…

因而,对于带参数的多重继承问题,应用 super() 会十分难用,不如应用显示调用,带来的小问题是局部类会反复初始化。

更进一步,在 python 中尽量不要应用多重继承,会让构造显得非常复杂,代码也变得软弱。在单继承场景中,则显示调用或者 super() 都能够应用,留神,要么全副类都显示调用,要么全副都用 super()

退出移动版