初めに
Pythonの多重継承の際の初期化や親クラスのメゾットの呼び出しで結構詰まったので、振り返りもかねて整理。多重継承の場合の継承する順番や初期化方法の違いによる、実行結果をまとめておいた。
実験に用いたソースコード
'''
足し算をする
'''
class math(object):
def __init__( self , val_a =1 , val_b = 1):
self.val_a = val_a
self.val_b = val_b
print( "math val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calc( self ):
return self.val_b + self.val_a
'''
引き算をする
'''
class sub(object):
def __init__( self , val_a =1 , val_b = 1):
self.val_a = val_a
self.val_b = val_b
print( "sub val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calc( self ):
return self.val_b - self.val_a
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
self.val_a = val_a
self.val_b = val_b
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
calc = calcuration()
print( calc.calc())
実験用に、こんな感じのプログラムを書いてみた。機能は以下の通り。
-
math()
:クラス内のcalc()
を呼び出すことで、足し算をする。 -
sub()
:クラス内のcalc()
を呼び出すことで、引き算をする。 -
calcuration()
:多重継承をするクラス。
出力
$ python3 main.py
Calcuration val_a:2val_b:2
4
現状のソースコードだと、self.val_a
とself.val_b
がcalcration()
内で初期化され、その値でmatch()
のcalc()
が呼び出されて計算が行われる。
多重継承したクラスの初期化
実験1:super().__init__()
で初期化
よくある、super().__init__()
で初期化する方法。
===== 省略 =====
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
#self.val_a = val_a
#self.val_b = val_b
super().__init__()
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
calc = calcuration()
print( calc.calc())
出力
$ python3 main.py
math val_a:1val_b:1
Calcuration val_a:1val_b:1
2
特に指定がない時は、多重継承するクラス時に列挙されているクラスのうちの最も左に記述されているクラスで初期化が行われるらしい。
実験2:super(calcuration,self).__init__()
で初期化
===== 省略 =====
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
self.val_a = val_a
self.val_b = val_b
super(calcuration,self).__init__()
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
calc = calcuration()
print( calc.calc())
出力
$ python3 main.py
math val_a:1val_b:1
Calcuration val_a:1val_b:1
2
出力は先ほどと同じ2が出力された。super(calcuration,self).__init__()
ではcalcuration()
自身で初期化するのではなく、その右のクラスで初期化を行っている。
今回だったら、calcuration
の次に書かれているクラスのmath
で初期化が行われたため、self.val_a
とself.val_b
がmath()
の値で初期化した時の値になっている。
実験3:super(math,self).__init__()
で初期化
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
#self.val_a = val_a
#self.val_b = val_b
super(math,self).__init__()
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
出力
hor:object_test sey0323$ python3 main.py
sub val_a:3val_b:2
Calcuration val_a:3val_b:2
5
先ほどの流れとおり、変数はsub()
で初期化された。しかしcalc()
関数はmath()
のものが呼ばれている。
継承するクラス内に、同一名のメゾットがある場合も初期化の際と同様に、最も左に書かれているクラスに定義されているものが呼ばれるらしい。
実験4:super(sub,self).__init__()
で初期化
じゃあ一番右にあるクラスで初期化したらどうなるのか?
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
#self.val_a = val_a
#self.val_b = val_b
super(sub,self).__init__()
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
calc = calcuration()
print( calc.calc())
出力
$ python3 main.py
Traceback (most recent call last):
File "main.py", line 37, in <module>
calc = calcuration()
File "main.py", line 35, in __init__
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
AttributeError: 'calcuration' object has no attribute 'val_a'
初期化の時はsuper( math , self)
の時はsub()
のように、1つ右側のが呼ばれるため、1番右端にあるsub
で初期化しようとするとエラーになる。
実験5:{親クラス}.__init__(self)
で初期化
こういう初期化方法もあるらしいので実験。
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
#self.val_a = val_a
#self.val_b = val_b
math.__init__(self)
#sub.__init__(self)
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
出力
$ python3 main.py
math val_a:1val_b:1
Calcuration val_a:1val_b:1
2
てっきりsub()
で初期化されると思っていたが、指定したmath()
のほうの初期化が実行された。
{親クラス}.__init__(self)
だと、指定した親クラスで初期化が実行されらしい。
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
#self.val_a = val_a
#self.val_b = val_b
#math.__init__(self)
sub.__init__(self)
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
calc = calcuration()
print( calc.calc())
出力
$ python3 main.py
sub val_a:3val_b:2
Calcuration val_a:3val_b:2
5
$
sub()
で初期化をしたら、変数の値は指定したクラスのもので初期化がされるが、関数は今までのルール通り、math
から探してが呼ばれている。
メゾットの実行
実験1:math()
クラスの値で初期化をして、その値で引き算をしたい1
ここまでひたすら初期化の実験をやってきて、sub()
クラスのcalc()
が全く呼ばれず,math()
のcalc()
による足し算ばかり実行されてきたので、ここからは何とかして引き算をやってもらいたい。
その場合はsuper( {使いたいメゾットのクラス} , self ).{使いたいメゾット}()
という風に指定すればよい。
初期化はmath
でしたいけど、関数は、'calc'のものを使いたいという時。
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
#self.val_a = val_a
#self.val_b = val_b
math.__init__(self)
#sub.__init__(self)
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calc( self ):
return super( math , self ).calc()
calc = calcuration()
print( calc.calc())
$ python3 main.py
math val_a:1val_b:1
Calcuration val_a:1val_b:1
0
self.val_a
とself.val_b
をmath()
クラスで初期化した上に、引き算ができた。
どうやら複数の親クラスで重複するメゾット名を持つメゾットを実行したい場合は、親クラスを指定して実行すれば良いようだ。
実験2:match
クラスの値で初期化をして、その値で引き算をしたい2
super( {利用したいメゾットの親クラスの1こ左のクラス} , self ).method()
のようにすることで、複数の親クラスでメゾット名が重複していても、指定した親クラスの方のメゾットを実行可能なことがわかった。
じゃあ次に、super( {利用したいメゾットの親クラスの1こ左のクラス} , self ).method()
のなかで、さらに複数の親クラスで共通している名前のメゾットを呼び出す場合は、どうなるのか?
以下の例だとmath
とsub
の両方の親クラスに、calc()
とcalcA()
があり、calcA()
がcalc()
を呼び出している状況の場合に、sub()
のクラスで初期化をして、引き算をしたいという状況。
期待する出力結果は0
失敗例
'''
足し算をする
'''
class math(object):
def __init__( self , val_a =1 , val_b = 1):
self.val_a = val_a
self.val_b = val_b
print( "math val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calc( self ):
return self.val_b + self.val_a
def calcA( self ):
return self.calc()
'''
引き算をする
'''
class sub(object):
def __init__( self , val_a =3 , val_b = 2):
self.val_a = val_a
self.val_b = val_b
print( "sub val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calc( self ):
return self.val_b - self.val_a
def calcA( self ):
print( "sub val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
return self.calc()
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
math.__init__(self)
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def child_calc( self ):
return super( math , self ).calcA()
calc = calcuration()
print( calc.child_calc())
出力
$ python3 main.py
math val_a:1val_b:1
Calcuration val_a:1val_b:1
sub val_a:1val_b:1
2
足し算になってしまった。
解決法1:メゾットの名前を変えてしまう
無理やりの解決として、最も右側にあるクラスの重複しているメゾットの名前を変えた。
'''
足し算をする
'''
class math(object):
def __init__( self , val_a =1 , val_b = 1):
self.val_a = val_a
self.val_b = val_b
print( "math val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calc( self ):
return self.val_b + self.val_a
def calcA( self ):
return self.calc()
'''
引き算をする
'''
class sub(object):
def __init__( self , val_a =3 , val_b = 2):
self.val_a = val_a
self.val_b = val_b
print( "sub val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def calcs( self ):
return self.val_b - self.val_a
def calcA( self ):
print( "sub val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
return self.calcs()
'''
多重継承するクラス。
'''
class calcuration( math , sub ):
def __init__( self , val_a =2 , val_b = 2):
math.__init__(self)
print( "Calcuration val_a:" + str(self.val_a) + "val_b:" + str(self.val_b) )
def child_calc( self ):
return sub.calcA(self)
calc = calcuration()
print( calc.child_calc())
色々試したところ、この親クラスのメゾット名重複問題の解決法は、この方法しか見つからなかった。親のあるメゾットで、そのメゾットが内部でほかのメゾット呼び出している場合って結構ありそうなんですけど。
最後に
リファレンスによると多重継承における名前クラスは深さ優先で、左から右の順序で見られるため、このような結果になるらしい。
9. クラス — Python 2.7.14 ドキュメント
多重継承を用いる際は、深さ優先について常にどのクラスが優先されているかを考えながら実装する必要がある。そのため、保守性や可読性が悪化するという問題を抱えており、推奨されていない言語もある。「多重継承は人類には早すぎる」といった文言をどこかで聞いたことがあるが、まさにそのとおりだなあと実感しました。
参考
[Python]クラス継承(super)
Python の super のメリットとデメリット - methaneのブログ