この記事では、Pythonの継承の使い方について解説します。
継承を使うことでクラスの性質を引継ぎ、拡張・変更したサブクラスを定義することができます。もちろん組み込み型のクラスを継承することもできます。
それでは、Pythonの継承の使い方を見ていきましょう!
継承(Inheritance)とは、クラスから派生した子クラスを定義することです。子クラスを定義することで親クラスの属性を引き継ぎ、さらに拡張することができます。
また、クラスを継承させることで親クラスが持つ属性が定義されていることを保証することができます。
継承元のクラスを「基底クラス・親クラス・スーパークラス」と呼び、継承先のクラスを「派生クラス・子クラス・サブクラス」と呼びます。
それでは、実際に継承を行う方法を見ていきましょう!
継承するには、クラス定義時の名前の後に()を使って継承させるクラスを指定します。下記の場合、Aが子クラス、Bが親クラスとなります。
class A(B): # なんらかの処理
さらに、1つのクラスに複数のクラスを継承させることもできます。
class A(B, C, ... ,N): # なんらかの処理
複数のクラスを継承することを「多重継承」と言いますが、菱形継承問題などもあり、構造が複雑化してしまうのであまり使われません。
サンプル
以下のコードは、Animalクラスを派生させ、2つのクラスを作成しています。
class Animal: def __init__(self, name): print('Animalクラスのコンストラクタが呼び出されました') self.name = name class Cat(Animal): pass class Dog(Animal): pass neko = Cat('猫ちゃん') print(neko.name) inu = Dog('わんちゃん') print(inu.name)
実行結果
Animalクラスのコンストラクタが呼び出されました 猫ちゃん Animalクラスのコンストラクタが呼び出されました わんちゃん
子クラスではコンストラクタを定義していないため、初期化にはAnimalクラスのコンストラクタが呼び出されました!
このように、親クラスで定義されていて子クラスで定義されていないメソッドを子クラスのインスタンスから呼び出した場合、親クラスのメソッドが呼び出されます。
オーバーライドとは、親クラスに定義されているメソッドと同じ名前のメソッドを子クラスで定義し、親クラスのメソッドを拡張・変更することを言います。
親クラスで定義されているメソッドと同名のメソッドを子クラスで定義します。
class 親: def メソッド(self): print('親のメソッド') class 子(親): # オーバーライド def メソッド(self): print('子のメソッド')
サンプル
以下のコードでは、Animalクラスで定義したメソッドを子クラスのDogクラスでオーバーライドしています。Catクラスでは何もしていません。
class Animal: # 親クラスのメソッド def cry(self): print('Animalのcryメソッド') # オーバーライドしないクラス class Cat(Animal): pass # オーバーライドするクラス class Dog(Animal): def cry(self): print('ワンワン!') neko = Cat() neko.cry() inu = Dog() inu.cry()
実行結果
Animalのcryメソッド ワンワン!
このように、親クラスのメソッドをオーバーライドすることで子クラスのメソッドを優先して呼び出すことができます。
super()を使うことで子クラスから親クラスを参照することができます。
子クラスのメソッド内からsuper()を使うことで親クラスにアクセスすることができます。コンストラクタやメソッドにアクセスすることもできます。
class 子(親): def メソッド(self): # 親にアクセス super()
サンプル
以下のコードでは、子クラスのコンストラクタから親クラスのコンストラクタを呼び出しています。
class 親: def __init__(self, value): print('親 コンストラクタ start') self.value = value print('親 コンストラクタ end') class 子(親): def __init__(self, value, data): print('子 コンストラクタ start') super().__init__(value) # 親クラスのコンストラクタ呼び出し self.data = data print('子 コンストラクタ end') child = 子('value', 'data') print(child.value) print(child.data)
実行結果
子 コンストラクタ start 親 コンストラクタ start 親 コンストラクタ end 子 コンストラクタ end value data
super()を使って親クラスのコンストラクタを呼び出すことで親クラスのコンストラクタを拡張することができます。
メソッドにも同様のことができます。
class 親: def メソッド(self): print('親 メソッド') class 子(親): def メソッド(self): super().メソッド() print('子 メソッド') child = 子() child.メソッド()
実行結果
親 メソッド 子 メソッド
継承しているクラスをさらに継承することができます。以下のコードは、D > C > B > Aクラスに渡って継承しています。
class A: def __init__(self, a): print('A init') self.a = a def method(self): print('A method') print(f'a: {self.a}') class B(A): def __init__(self, a, b): print('B init') super().__init__(a) self.b = b def method(self): print('B method') print(f'a: {self.a}, b: {self.b}') class C(B): def __init__(self, a, b, c): print('C init') super().__init__(a, b) self.c = c def method(self): print('C method') print(f'a: {self.a}, b: {self.b}, c: {self.c}') class D(C): def __init__(self, a, b, c, d): print('D init') super().__init__(a, b, c) self.d = d def method(self): print('D method') print(f'a: {self.a}, b: {self.b}, c: {self.c}, d: {self.d}') print('Aのインスタンス化') a = A(1) a.method() print('Bのインスタンス化') b = B(1, 2) b.method() print('Cのインスタンス化') c = C(1, 2, 3) c.method() print('Dのインスタンス化') d = D(1, 2, 3, 4) d.method()
実行結果
Aのインスタンス化 A init A method a: 1 Bのインスタンス化 B init A init B method a: 1, b: 2 Cのインスタンス化 C init B init A init C method a: 1, b: 2, c: 3 Dのインスタンス化 D init C init B init A init D method a: 1, b: 2, c: 3, d: 4
上記コードでは、super()で親クラスのコンストラクタを呼び出していましたが、さらに上の世代のクラスを参照することもできます。
例えば、DクラスからBクラスやAクラスを参照することができます。
以下のコードでは、Dクラスのコンストラクタ内でBクラスを参照し、methodメソッドでAクラスを参照しています。
class D(C): def __init__(self, a, b, d): print('D init') # Bクラスのコンストラクタの呼び出し super(C, self).__init__(a, b) self.d = d def method(self): # Aクラスのmethodの呼び出し super(B, self).method() print('Dのインスタンス化') d = D(1, 2, 4) d.method()
実行結果
Dのインスタンス化 D init B init A init a: 1
継承するとメソッド順序解決でメソッドが呼び出される優先順位が決められます。
今回の場合は、単純な直列の継承なので子が優先され、D > C > B > Aクラスという順序になります。そして、super関数の第一引数に型を渡すことで、その型の直後から検索されるようになります。
コードを見てみると、コンストラクタのsuper関数では、Cクラスが渡されているのでB > Aクラスという順序で検索されます。
methodメソッド内のsuper関数では、Bクラスが渡されているのでAクラスのみが検索されています。
1つのクラスに複数のクラスを継承させることができます。以下のコードは、A・BクラスをCクラスに継承しています。
class A: def __init__(self, a): print('A init') self.a = a def method(self): print(f'a: {self.a}') class B: def __init__(self, b): print('B init') self.b = b def method(self): print(f'b: {self.b}') class C(A, B): def __init__(self, a, b, c): print('C init') super().__init__(a) self.c = c def method(self): print(f'a: {self.a}, c: {self.c}') c = C(1, 2, 3) c.method()
実行結果
C init A init a: 1, c: 3
このコードでは、super()でAクラスが参照されています。
上記コードでは、Aクラスしか参照していなかったのでBクラスも参照してみます。
class C(A, B): def __init__(self, a, b, c): print('C init') super().__init__(a) # Bクラスの参照 super(A, self).__init__(b) self.c = c def method(self): print(f'a: {self.a}, b: {self.b}, c: {self.c}') c = C(1, 2, 3) c.method()
実行結果
C init A init B init a: 1, b: 2, c: 3
このように、super関数の第一引数に Aクラス を指定することでBクラスを参照することができます。
継承させる順番を変えることで検索順序を変更することができます。左側に記述するほど優先 されます。
# 継承させる順番をB > Aに変更 class C(B, A): def __init__(self, a, b, c): print('C init') # Bクラスが参照されている super().__init__(a) # Aクラスが参照されている super(B, self).__init__(b) self.c = c def method(self): print(f'a: {self.a}, b: {self.b}, c: {self.c}') c = C(1, 2, 3) c.method()
実行結果
C init B init A init a: 2, b: 1, c: 3
C > B > Aクラスの順序で呼び出されるようになりました。
親クラスのメソッドをオーバーライドしていない場合、検索順序には親クラスのメソッドが評価されます。
class Zero: def method(self): print('Zero') class A(Zero): pass class B(Zero): pass class C(Zero): def method(self): print('C') class Tail(A, B, C): pass t = Tail() t.method()
実行結果
C
検索順序的には、CクラスよりもA・Bクラスの方が早いですが、メソッドをオーバーライドしてない為、A・BクラスはZeroクラスのメソッドを参照しています。そのため、検索順序はC > Zeroになっています。
mroメソッドを使うことでメソッド順序解決リストを取得できます。
クラス.mro()
サンプル
検索順序を確認してみましょう!
class Zero: pass class A(Zero): pass class B(Zero): pass class Tail(A, B): pass print(Tail.mro())
実行結果
[<class '__main__.Tail'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Zero'>, <class 'object'>]
Tail > A > B > Zero になっているのがわかる。
isinstance()関数を使うことで特定のクラスを継承しているか判別することができます。
isinstance(object, classinfo)
object引数がclassinfo引数のインスタンスか、またはサブクラスのインスタンスの場合にTrueが返されます。
サンプル
以下のコードは、Parentクラスを継承しているか判定し、継承していた場合はParentクラスの属性を呼び出しています。
class Parent: def __init__(self): self.value = 'parent' # Parentを継承したクラス class Child(Parent): pass # 継承していないクラス class NoParent: pass # インスタンス化 c = Child() n = NoParent() def func(obj): # クラス名の出力 print(obj.__class__) if isinstance(obj, Parent): print('Parentクラスを継承しています') print(f'value: {obj.value}') # Parentが継承されていれば必ず定義されている else: print('Parentクラスを継承していません') func(c) func(n)
実行結果
<class '__main__.Child'> Parentクラスを継承しています value: parent <class '__main__.NoParent'> Parentクラスを継承していません
このように、特定の親クラスを継承しているかどうかを判別することで、そのクラスが親クラスで定義されている属性を呼び出せるかどうかを判別できます。
継承はとても便利な機能ですが、実は危険性も孕んでいます。
継承を使うごとにソースコードの見通しがどんどん悪くなっていき、親クラスの責任と影響が不明確になってしまうのです。つまり、どこをいじったらどのような変化が起こるかわからないコードになりやすいのです。
なので、最近では合成を使うことが推奨されています。
皆さんも継承の使い方には注意してください。
この記事では、Pythonの継承について解説しました。
継承からのオーバーライドは覚えておきましょう。
def 親: def メソッド名(self): print('親メソッド') def 子(親): def メソッド名(self): print('子メソッド')
それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ
View Comments