基本的な継承
まず、シンプルな例です。
Topクラス(親クラス)を継承したBottomクラス(子クラス)を用意しました。
class Top: # 親クラス
def method_top(self):
print("Topクラスの method_top")
class Bottom(Top): # 子クラス
def method_top(self):
print("Bottomクラスの method_top")
def method_bottom(self):
print("method_bottom")
bottom = Bottom()
bottom.method_top() # Bottomクラスの method_top
bottom.method_bottom() # method_bottom
Bottomクラスでは、Topクラスで定義しているmethod_topメソッドをオーバーライドして書き換えています。また、Bottomクラス特有のmethod_bottomメソッドも追加で定義しています。
このBottomクラスのインスタンスから呼び出したmethod_topメソッドは、親のTopクラスのものではなく、子のBottomクラスのものが呼び出されていることが確認できます。
多重継承
Pythonでは、JavaやC#ではできない、「多重継承」ができます。
複数のクラスを継承したクラスを作れるということですね。
class One:
def one(self):
print("one")
class Two:
def two(self):
print("two")
class Hoge(One, Two): # OneクラスとTwoクラスを多重継承している
def three(self):
print("three")
hoge = Hoge()
hoge.one() # one
hoge.two() # two
hoge.three() # three
Hogeクラスのインスタンスは、親のOneクラス、Twoクラスのメソッドを呼び出せています。
この例ではそれぞれ違う名前のメソッドを定義して呼び出していますが、同じ名前のメソッドを複数の親クラスで定義していたらどうでしょうか。
__init__メソッドで試してみます。
class One:
def __init__(self, num):
self.num = num # 引数numをそのまま初期化
class Two:
def __init__(self, num):
self.num = num * 2 # 引数numを2倍して初期化
class Hoge(One, Two):
pass # 何もしない
hoge = Hoge(5)
print(hoge.num) # 5 (Oneの__init__が呼び出されている)
Oneクラスの__init__が呼び出されています。Oneクラスは、継承順で先頭になっているクラスですね。
Pythonの多重継承では、メソッド探索の順序は「MRO(Method Resolution Order)」 に基づきます。
MROは、「継承順の左優先+継承関係の一貫性」に基づいた順序です。
- まず子クラスの中でメソッドを検索
- 見つからなければ、子クラスのMRO順(継承リストに基づく順序)で親クラスをたどる
- 一番最初にヒットしたメソッドが呼び出される
なので、子のHogeクラスで__init__を定義していたら、Hogeクラスの__init__が呼び出される結果になりますね。
ダイアモンド継承
このMRO順について、もう少しだけ複雑な多重継承の例を使って見てみます。
以下は、継承ツリーの形状から、ダイアモンド継承(菱形継承)と言われる構造です。
class A:
def greet(self):
print("A")
class B(A):
def greet(self):
print("B")
class C(A):
def greet(self):
print("C")
class D(B, C):
pass
d = D()
d.greet() # B
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
MRO順は、__mro__属性を見て確認できます。
孫のDクラス、子のBクラスやCクラスを検索した後、BクラスやCクラスの親であるAクラス(そしてobjectクラス)もしっかりと検索していることがわかります。
super()
最後に、ついでになりますがsuper関数についても見てみます。
以下のコードは、ダイアモンド継承の説明で使用したコードに、super()を足したものです。
このとき、出力結果はどうなるのでしょうか?
class A:
def greet(self):
print("A")
class B(A):
def greet(self):
print("B")
super().greet()
class C(A):
def greet(self):
print("C")
super().greet()
class D(B, C):
pass
d = D()
d.greet()
もしsuper()が「親クラスを直接呼ぶ」ものであれば、結果は以下になっています。
B
A
C
A
A
しかし、super()は「MRO順の次のクラスを呼ぶ」ので、MRO順「D → B → C → A → object」に従います。
よって、
- Dクラスを検索するが、
greetメソッドがない - Bクラスを検索したら、
greetメソッドがあった!(print("B")) - Bクラスの中の
greetメソッドにsuper().greet()がある。
MRO順で、現在のBの次のCクラスのgreetメソッドが呼ばれる - Cクラスを検索したら、
greetメソッドがあった!(print("C")) - Cクラスの中の
greetメソッドにsuper().greet()がある。
MRO順で、現在のCの次のAクラスのgreetメソッドが呼ばれる - Aクラスを検索したら、
greetメソッドがあった!(print("A"))
したがって、この例の出力結果は
B
C
A
といった具合になります。
複雑ですね
参考文献