こんにちは、かつコーチです。
クラスの基本を覚えると、次に気になってくるのが継承です。
「似たクラスを何個も作るのが面倒」
「共通の処理はまとめたいけど、一部だけ変えたい」
そんな悩みを解決してくれるのが、継承とオーバーライドという仕組みです。
この記事では、クラスの継承・super()の使い方・メソッドのオーバーライドを、実例を交えて解説します。
読み終える頃には、共通処理を親クラスにまとめ、子クラスで必要な部分だけ拡張できるようになります。
クラスの継承の基本
継承とは何か
継承とは、あるクラスの機能を引き継いで、新しいクラスを作る仕組みです。
元になるクラスを親クラス(基底クラス)、引き継いで作るクラスを子クラス(派生クラス)と呼びます。
例えば「動物」という親クラスを作れば、「犬」「猫」といった子クラスは、共通の機能をそのまま使い回せます。
継承の書き方
Pythonでは、クラス定義時に()の中に親クラスを指定するだけで継承できます。
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}はごはんを食べています")
class Dog(Animal):
pass
my_dog = Dog("ポチ")
my_dog.eat()
実行結果は次の通りです。
ポチはごはんを食べています
Dog(Animal)と書くだけで、DogクラスはAnimalクラスの__init__やeatメソッドをそのまま使えるようになります。
Dog側には何も書いていないのに、動作している点に注目してください。
子クラス独自のメソッドを追加する
子クラスには、親クラスにない独自のメソッドを自由に追加できます。
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}はごはんを食べています")
class Dog(Animal):
def bark(self):
print(f"{self.name}: ワン!")
my_dog = Dog("ポチ")
my_dog.eat()
my_dog.bark()
実行結果は次の通りです。
ポチはごはんを食べています
ポチ: ワン!
eatは親クラスAnimalから引き継いだメソッド、barkはDogだけが持つ独自メソッドです。
super()の使い方
initをオーバーライドしつつ親の処理も呼ぶ
子クラスで独自の初期化処理を追加したい場合、__init__を子クラス側で定義し直すことになります。
このとき、親クラスの__init__をゼロから書き直すのは非効率です。
super()を使えば、親クラスの__init__をそのまま呼び出しつつ、子クラス独自の処理を追加できます。
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
my_dog = Dog("ポチ", "柴犬")
print(my_dog.name)
print(my_dog.breed)
実行結果は次の通りです。
ポチ
柴犬
super().__init__(name)が、親クラスAnimalの__init__を呼び出し、self.nameを設定しています。
その後、子クラス独自のself.breedを追加している流れです。
super()を使わない場合との比較
super()を使わずに、同じ処理を子クラスだけで書くこともできます。
class Dog(Animal):
def __init__(self, name, breed):
self.name = name # 親クラスと同じ処理を重複して書いている
self.breed = breed
この書き方でも一応動きますが、親クラスの初期化処理が複雑になったときに、子クラス側も毎回修正が必要になります。
super()を使えば、親クラスの処理は親クラスに任せられるため、修正箇所が1か所で済み、保守性が高まります。
メソッドのオーバーライド
オーバーライドとは何か
オーバーライドとは、親クラスで定義されたメソッドを、子クラス側で上書きして再定義することです。
同じ名前のメソッドでも、クラスごとに異なる振る舞いをさせたいときに使います。
class Animal:
def cry(self):
print("...")
class Dog(Animal):
def cry(self):
print("ワン!")
class Cat(Animal):
def cry(self):
print("ニャー")
animals = [Dog(), Cat(), Animal()]
for animal in animals:
animal.cry()
実行結果は次の通りです。
ワン!
ニャー
...
同じcry()という呼び出し方でも、実際に呼ばれるメソッドはインスタンスのクラスによって切り替わります。
この仕組みをポリモーフィズム(多態性)と呼びます。
オーバーライドした中でも親の処理を活用する
オーバーライドしたメソッドの中でも、super()を使えば親クラスの処理を呼び出せます。
親の処理を活かしつつ、一部だけ追加したい場合に便利です。
class Animal:
def introduce(self):
print("私は動物です")
class Dog(Animal):
def introduce(self):
super().introduce()
print("その中でも犬です")
my_dog = Dog()
my_dog.introduce()
実行結果は次の通りです。
私は動物です
その中でも犬です
super().introduce()で親の処理を実行したあと、子クラス独自のprintを追加しています。
よくあるつまずきポイント・エラー対処
super()の引数を書きすぎてエラーになる
筆者が実際にハマったのが、Python 2の書き方の記憶につられてsuper()に余計な引数を渡してしまったケースです。
❌ Before(エラーになる書き方)
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super(self, Dog).__init__(name)
self.breed = breed
my_dog = Dog("ポチ", "柴犬")
実行すると、次のエラーメッセージが表示されます。
TypeError: super() argument 1 must be type, not Dog
Python 2ではsuper(クラス名, self)という書き方が必要でしたが、引数の順番を逆に覚えていたのが原因でした。
✅ After(正しい書き方)
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
my_dog = Dog("ポチ", "柴犬")
print(my_dog.breed)
Python 3では引数なしのsuper()だけで、自動的に親クラスとインスタンスを判別してくれます。
引数を渡す書き方は原則不要だと覚えておくと、このエラーを避けられます。
まとめ
この記事のポイント
- 継承は
class 子クラス(親クラス)という書き方で実現する - 子クラスは親クラスのメソッドをそのまま引き継げる
super()を使うと、親クラスの処理を呼び出しつつ子クラス独自の処理を追加できる- 同名メソッドを子クラスで再定義することをオーバーライドと呼ぶ
- Python 3の
super()は引数なしで書くのが基本
次に読むべき記事
- クラスとオブジェクト指向の基本
- デコレータの基本:関数を拡張する仕組み
- プロパティ(@property)でgetter/setterを実装する
タグ: Python, 中級者向け, 基本文法