在python中,關於類的繼承有很多場景和知識點。今天聚焦在一個場景:有一個父類A,類中定義了某個問題中的通用屬性和方法(即后面的子類都需要用到),在子類B中需要繼承這些屬性和方法,同時添加自己特有的屬性和方法,應該如何實現?
在子類中,繼承並初始化父類屬性的方式有兩種:
-
顯示調用父類的初始化函數,並且對屬性進行初始化;
-
通過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/147692
因此,對於帶參數的多重繼承問題,使用super()會非常難用,不如使用顯示調用,帶來的小問題是部分類會重復初始化。
更進一步,在python中盡量不要使用多重繼承,會讓結構顯得非常復雜,代碼也變得脆弱。在單繼承場景中,則顯示調用或者super()都可以使用,注意,要么全部類都顯示調用,要么全部都用super()
