サブクラスからスーパークラスを初期化
サブクラスからスーパークラスを初期化する古いやり方
⇒サブクラスのインスタンスでスーパークラスの__init__メソッドを直接呼び出す
class Base(object):
def __init__(self, num):
self.num = num
# サブクラス
class Inheritance(Base):
def __init__(self):
Base.__init__(self, 10)
この方法では、問題が生じる場合がある。
多重継承の場合を考えてみる
⇒多重継承を行った場合、__init__メソッドの呼び出し順序がサブクラス間で規定されていないというような問題にぶつかる。
このコードは、想像通りの結果を出力してくれる。
class Base(object):
def __init__(self, num):
self.num = num
class Multiplication(object):
def __init__(self):
self.num *= 10
class Addition(object):
def __init__(self):
self.num += 25
# 継承順と呼び出し順が同じ
class Multiplex(Base, Multiplication, Addition):
def __init__(self, num):
Base.__init__(self, num)
Multiplication.__init__(self)
Addition.__init__(self)
N = Multiplex(5)
print(N.num)
>>>75
一方、以下のコードを考えてみよう
class Base(object):
def __init__(self, num):
self.num = num
class Multiplication(object):
def __init__(self):
self.num *= 10
class Addition(object):
def __init__(self):
self.num += 25
# 継承順と呼び出し順が異なる
class Another_Multiplex(Base, Addition, Multiplication):
def __init__(self, num):
Base.__init__(self, num)
Multiplication.__init__(self)
Addition.__init__(self)
N = Another_Multiplex(5)
print(N.num)
>>>75
このコードは、継承順(Another_MUltiplexの引数)がBaseクラス⇒Additionクラス⇒ Multiplicationクラス となっているので、一見 (5 + 25) * 10 = 30になると予測する人も出てきそうだが実際の出力結果は75になる。
これは、クラスの順序と__init__呼び出しの順序が異なるとコードが見ずらいという問題が発生する。
ダイヤモンド継承の場合を見てみる
ダイヤモンド継承では、共通クラスの__init__メッソドが何回も実行されることによって問題が発生しうる。
class Base(object):
def __init__(self, num):
self.num = num
class Multiplication(Base):
def __init__(self, num):
Base.__init__(self, num)
self.num *= 10
class Addition(Base):
def __init__(self, num):
Base.__init__(self, num)
self.num += 6
# 継承順と呼び出し順が異なる
class Multiplex(Addition, Multiplication):
def __init__(self, num):
Multiplication.__init__(self, num)
Addition.__init__(self, num)
N = Multiplex(5)
print(N.num)
>>>11
5 * 10 + 6 = 56になると思いきや、出力結果は11になる。
これは、 MultiplexクラスでMultiplication.__init__がおこなわれ、Addition.__init__が実行されるときに、問題が発生する。なんとAddition.__init__が実行時にBaseクラスの__init__メソッドが2回目の呼び出しをされて、Multiplication.__init__の実行結果が帳消しにされてしまう。
この問題を解決するためにPythonではsuper関数と標準メッソド解決順序(Method Resolution Order 通称: MRO)が用意されました。
⇒super関数を使うとダイヤモンド継承時に共通するスーパークラスが一度しか実行されない。
⇒MROは、C3線形化と呼ばれるアルゴリズムに従ってスーパークラスの初期化順序を定義してくれる。
class Base(object):
def __init__(self, num):
self.num = num
class Multiplication(Base):
def __init__(self, num):
super().__init__(num)
self.num *= 10
class Addition(Base):
def __init__(self, num):
super().__init__(num)
self.num += 6
# 継承順と呼び出し順が異なる
class Multiplex(Addition, Multiplication):
def __init__(self, num):
super().__init__(num)
N = Multiplex(5)
print(N.num)
>>>56
super関数を用いるとBase.__init__は一度しか実行されないので期待通りの結果が出力されました。MROで定義されたクラス順序で実行されている。
ポイント
* MROによってダイヤモンド継承の問題を解決することができる * スーパークラスの初期化には、super関数を使おう ⇒コードが少なくなるだけではなく、保守性の向上が期待できるため。参考文献
Effective Python