Pythonの多重継承の際の初期化とメゾットについて

# 初めに
Pythonの多重継承の際の初期化や親クラスのメゾットの呼び出しで結構詰まったので、振り返りもかねて整理。多重継承の場合の継承する順番や初期化方法の違いによる、実行結果をまとめておいた。

## 実験に用いたソースコード
```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()`:多重継承をするクラス。


#### 出力
```sh:
$ python3 main.py
Calcuration  val_a:2val_b:2
4
```
現状のソースコードだと、`self.val_a`と`self.val_b`が`calcration()`内で初期化され、その値で`match()`の`calc()`が呼び出されて計算が行われる。


# 多重継承したクラスの初期化
## 実験1：`super().__init__()`で初期化
よくある、`super().__init__()`で初期化する方法。

```python:
===== 省略 =====

'''
多重継承するクラス。
'''
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())
```
### 出力
```sh:
$ python3 main.py
math  val_a:1val_b:1
Calcuration  val_a:1val_b:1
2
```
特に指定がない時は、多重継承するクラス時に列挙されているクラスのうちの最も左に記述されているクラスで初期化が行われるらしい。

## 実験2：`super(calcuration,self).__init__()`で初期化

```python:
===== 省略 =====

'''
多重継承するクラス。
'''
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())
```
### 出力
```sh:
$ python3 main.py
math  val_a:1val_b:1
Calcuration  val_a:1val_b:1
2
```

出力は先ほどと同じ２が出力された。`super(calcuration,self).__init__()`では`calcuration()`自身で初期化するのではなく、その右のクラスで初期化を行っている。
今回だったら、`calcuration`の次に書かれているクラスの`math`で初期化が行われたため、`self.val_a` と`self.val_b`が`math()`の値で初期化した時の値になっている。

## 実験3：`super(math,self).__init__()`で初期化
```python:
'''
多重継承するクラス。
'''
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) )

```
### 出力
```sh:
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__()`で初期化

じゃあ一番右にあるクラスで初期化したらどうなるのか？

```python:
'''
多重継承するクラス。
'''
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())
```
### 出力
```sh:
$ 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)`で初期化
こういう初期化方法もあるらしいので実験。

```python:
'''
多重継承するクラス。
'''
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) )

```
### 出力
```sh:
$ python3 main.py
math  val_a:1val_b:1
Calcuration  val_a:1val_b:1
2
```
てっきり`sub()`で初期化されると思っていたが、指定した`math()`のほうの初期化が実行された。
`{親クラス}.__init__(self)`だと、指定した親クラスで初期化が実行されらしい。

```python:
'''
多重継承するクラス。
'''
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())

```
### 出力
```sh:
$ 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'のものを使いたいという時。

```python:
'''
多重継承するクラス。
'''
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())

```

```sh:
$ 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`

### 失敗例
```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

    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())

```
#### 出力
```sh:
$ python3 main.py
math  val_a:1val_b:1
Calcuration  val_a:1val_b:1
sub  val_a:1val_b:1
2
```

足し算になってしまった。

### 解決法1:メゾットの名前を変えてしまう
無理やりの解決として、最も右側にあるクラスの重複しているメゾットの名前を変えた。

```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

    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 ドキュメント](https://docs.python.jp/2.7/tutorial/classes.html#inheritance)

多重継承を用いる際は、深さ優先について常にどのクラスが優先されているかを考えながら実装する必要がある。そのため、保守性や可読性が悪化するという問題を抱えており、推奨されていない言語もある。「多重継承は人類には早すぎる」といった文言をどこかで聞いたことがあるが、まさにそのとおりだなあと実感しました。

# 参考
[[Python]クラス継承(super)](https://qiita.com/Kodaira_/items/42dfe18c81af98bf0db3)
[Python の super のメリットとデメリット - methaneのブログ](https://methane.hatenablog.jp/entry/20081227/1230400144)