「同じようなコードを何度も書いていて、どこかでまとめられないかな?」そんな悩みは、Pythonの継承を使うとぐっと楽になります。
継承は、オブジェクト指向プログラミングの基本機能のひとつで、既存のクラス(親クラス)の性質や機能を、新しいクラス(子クラス)に引き継ぐ仕組みです。共通部分を親クラスに集約することで、コードの重複を減らし、見通しの良いプログラムを作れます。
今回は、Pythonでの継承を使ってコードを再利用する考え方と実践方法を、やさしく丁寧に解説します。
まずは継承のイメージをつかみましょう。
親クラスが持っている属性やメソッドは、子クラスでもそのまま使えます。また、子クラス側で同じ名前のメソッドを書き直すと、親クラスの動作を上書き(オーバーライド)できます。
次のシンプルな例で雰囲気をつかんでください。
class Animal:
def speak(self):
return "Some sound"
class Dog(Animal):
def speak(self):
return "Woof!"
# インスタンスを作成
animal = Animal()
dog = Dog()
# メソッドの呼び出し
print(animal.speak()) # 出力: Some sound
print(dog.speak()) # 出力: Woof!
ここでは、Animal が親クラス、Dog が子クラスです。Dog は Animal を継承しているため、Animal の性質を受け継ぎつつ、speak を自分用に書き換えています。同じ「speak」という名前でも、Dog のインスタンスでは「Woof!」と犬らしい振る舞いになります。これがオーバーライドです。「親の共通ルールは引き継ぎつつ、必要なところだけ自分流にする」というのが継承の基本的な使い方です。
「再利用って言うけど、どれくらい楽になるの?」と疑問に思うかもしれません。
ここでは、わざと重複したコードを書いてから、継承を使ってスマートに直す手順を体験してみます。 まずは継承を使わない場合です。車とバイク、それぞれに似たような処理を書いています。
# 継承なしの例(重複が多い)
class Car:
def __init__(self, brand):
self.wheels = 4
self.brand = brand
def drive(self):
return f"{self.brand} car: Driving with {self.wheels} wheels"
class Bike:
def __init__(self, brand):
self.wheels = 2
self.brand = brand
def drive(self):
return f"{self.brand} bike: Driving with {self.wheels} wheels"
# インスタンスを作成
car = Car("Toyota")
bike = Bike("Yamaha")
# メソッドの呼び出し
print(car.drive()) # 出力: Toyota car: Driving with 4 wheels
print(bike.drive()) # 出力: Yamaha bike: Driving with 2 wheels
よく見ると、wheels という共通の情報や drive のメッセージ形式が、クラスごとに重複しています。 重複は、後から仕様を変えるときに修正漏れを生みやすく、バグの温床になりがちです。
では継承を使って、共通部分を親クラスにまとめてみましょう。
class Vehicle:
def __init__(self, wheels):
self.wheels = wheels
def drive(self):
return f"Driving with {self.wheels} wheels"
class Car(Vehicle):
def __init__(self, brand):
super().__init__(4) # 車は4輪
self.brand = brand
def drive(self):
return f"{self.brand} car: " + super().drive()
class Bike(Vehicle):
def __init__(self, brand):
super().__init__(2) # バイクは2輪
self.brand = brand
def drive(self):
return f"{self.brand} bike: " + super().drive()
# インスタンスを作成
car = Car("Toyota")
bike = Bike("Yamaha")
# メソッドの呼び出し
print(car.drive()) # 出力: Toyota car: Driving with 4 wheels
print(bike.drive()) # 出力: Yamaha bike: Driving with 2 wheels
この書き方では、車輪の数や基本的な走行メッセージを Vehicle に集約しました。Car と Bike は、ブランド名などの「それぞれ固有の違い」だけに集中して書けています。子クラスの __init__ では super() を使って親クラスの初期化を呼び出し、wheels を設定しています。さらに drive も、親クラスの基本メッセージにブランド名を付け足す形にすることで、処理の再利用と拡張を両立しています。「共通は親へ、違いは子へ」。これが継承によるコード再利用の王道パターンです。
親クラスは「型としての共通点」を表す場所です。
Vehicle は「何輪で走る」という共通の振る舞いを提供し、Car や Bike は「ブランド名」や「表示の仕方」といった具体的な差分だけを足します。 結果として、DRY(Don’t Repeat Yourself:同じことを繰り返さない)という原則を守りやすくなり、修正も一箇所で済みます。
たとえば走行メッセージを少し変えたくなった場合、Vehicle.drive
の文字列を直すだけで Car と Bike の両方に反映されます。たくさんの派生クラスがあっても、基本仕様の変更が一回で届くのは大きなメリットです。
コードの中身をさらに深ぼってみましょう。
Car("Toyota")
と書いた瞬間、まず Car の __init__ が走ります。その中で super().__init__(4) が呼ばれるので、Vehicle の初期化が実行され、wheels に 4 が入ります。
drive を呼ぶと、Car の drive が最初に見つかり、メッセージの前半を組み立てた後、super().drive()
で親のメソッドを呼び出して後半を結合します。
親の処理を活かしつつ、子の文脈をプラスできるのが、継承と super() の良いところです。
ここまで読んで、「トラックならどう書くだろう?」と気になりませんか? Truck は多くの場合 6 輪以上です。Vehicle を親にして、Truck クラスを追加してみましょう。積載量の属性を持たせたり、drive のメッセージに「heavy load」などを加えるのも面白いでしょう。親の仕組みを使い回しながら、差分だけを足せることを実感できるはずです。
継承を使うべきか判断に迷ったときは、「A は B の一種(A is a B)と言えるか?」で考えるとスッキリします。
Car は Vehicle の一種なので継承は自然です。一方で、「ユーザーはログを“持つ”」のような関係は has-a(持っている)で表すのが適切で、継承よりも「合成(コンポジション)」と呼ばれる別の手法が向いています。継承は強力ですが、何でも継承にするとクラス階層が複雑になりがちです。共通の性質をまとめたいとき、そして is-a が成り立つときに使う、という方針が安全です。
Python の継承を使うと、親クラスに共通処理を集め、子クラスでは必要な差分だけを実装できます。これによりコードが短く、読みやすく、変更に強くなります。オーバーライドで振る舞いをカスタマイズし、super() で親の処理を再利用すれば、DRY を自然に実現できます。まずは自分のコードの中で重複している部分を探し、「共通は親、違いは子」という観点で置き換えてみましょう。少しずつでも、継承によるコード再利用の効果を実感できるはずです。