Q1次のクラス定義のうち、DogがAnimalを継承する正しい書き方はどれですか?
クラスの継承 — 既存クラスの機能を引き継ぐ
Pythonのクラスの継承をPerson/Employeeで解説。class子(親): で属性とメソッドを引き継ぐ書き方、同名メソッドの上書き(オーバーライド)、super().__init__で属性を追加するパターンを確認できます。
前回は__add__や__str__といった特殊メソッドで、自作クラスに組み込み操作を教える書き方を整理しました。今回はオブジェクト指向の重要な要素の一つである継承について解説します。
なぜ継承を使うのか
アプリを作っていると「ほとんど同じだけど、一部だけ違う」クラスを複数用意したくなる場面が出てきます。たとえば「お客様(Customer)」と「従業員(Employee)」と「管理者(Admin)」のように、nameとemailを持ってgreeting()を返す部分は共通で、それぞれ独自の属性やメソッドだけが追加で要る、というケースです。
3 つのクラスにそれぞれ同じname / email / greeting()を書いてもいいのですが、全く同じコードが重複してしまい、修正が入るたびに 3 箇所を直すことになります。共通部分を親クラスにまとめ、各クラスは「親クラスを引き継ぎつつ、差分だけ書く」 ようにすればこの問題を解決できます。これが 継承(inheritance) です。
Person)に集めて、差分だけを子クラス(Employee)に書く。name / greetingを毎回書き直す必要がなくなる。親クラスを継承する — class 子(親):
継承の書き方はシンプルです。子クラスを定義するときに、クラス名のあとに丸括弧で親クラスを指定します。これで、子クラスは親クラスが持つ属性とメソッドをそのまま引き継ぐことができます。
以下は、name / ageを持って自己紹介するPersonクラスを親、それを継承するEmployeeを子クラスにした例です。Employee側にはメソッドを 1 つも書いていませんが、親の greeting() がそのまま呼び出せているのが分かります。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greeting(self):
print(f"Hello, {self.name}")
def say_age(self):
print(f"{self.age} years old")
class Employee(Person): # ← Person を継承
pass # 中身は空でもよい
e = Employee("田中", 45)
e.greeting() # Hello, 田中 ← 親のメソッドが呼べる
e.say_age() # 45 years old
- 属性: name / age
- メソッド: greeting() / say_age()
- __init__(self, name, age)
- 中身は 空(pass)
- → 親の属性・メソッドをそのまま引き継ぐ
- Employee("田中", 45) で親の __init__ が呼ばれる
メソッドのオーバーライド — 同じ名前で書き換える
継承するだけだと親の振る舞いそのままですが、子クラス側で親と同じ名前のメソッドを書くと、子クラスのほうが優先的に呼ばれます。これがオーバーライド(override)です。
たとえばEmployee用の挨拶を「Hello, Employee 田中」に変えたいときは、子クラスでgreetingを再定義します。Personインスタンスから呼べば親の挨拶、Employeeインスタンスから呼べば子の挨拶 — というふうに、呼び出すインスタンスがどちらのクラスかによって振る舞いが切り替わります。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greeting(self):
print(f"Hello, {self.name}")
class Employee(Person):
def greeting(self): # ← 同じ名前で再定義 = オーバーライド
print(f"Hello, Employee {self.name}")
p = Person("佐藤", 30)
e = Employee("田中", 45)
p.greeting() # Hello, 佐藤
e.greeting() # Hello, Employee 田中 ← 子クラスのほうが呼ばれる
super() で親クラスのメソッドを呼ぶ
オーバーライドしつつも、親の処理は活かしてその上に追加で書きたい — そんな場面ではsuper()を使います。super().method()と書くと、今のクラスの親クラスにある同名メソッドを呼ぶことができます。
たとえば「親のgreetingでHello, 田中を出力した上で、子のgreetingで 1 行追加したい」という場合に、子のgreetingの中でsuper().greeting()を最初に呼んでから自分の処理を書きます。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greeting(self):
print(f"Hello, {self.name}")
class Employee(Person):
def greeting(self):
super().greeting() # ① 親の挨拶を先に出す
print(f"I'm Employee {self.name}") # ② 子の挨拶を続ける
Employee("田中", 45).greeting()
# Hello, 田中
# I'm Employee 田中
greetingの中でsuper().greeting()を呼ぶと、親のgreetingがそのまま実行される。selfは自動で渡されるので書かなくてよい。__init__ をオーバーライドして属性を増やす
super()がいちばん活躍するのが__init__のオーバーライドです。子クラスで「親の属性に加えて、子だけが持つ属性を追加したい」というケース — たとえばEmployeeにはphone_numberも持たせたい、というときです。
この場合、__init__ を子クラスでも書き直すことになりますが、親の初期化処理(self.name = name など)は super().__init__(name, age) で 1 行で済ませて、その後に追加分だけ書くのが定石です。これでname / ageの代入を 2 重に書かずに済みます。
Employee(...)を呼ぶと子の__init__が走る → super().__init__(name, age)で親がname / ageを代入 → そのあと子がphone_numberを追加。親の属性 + 子の属性が揃った状態でインスタンスが完成する。class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Employee(Person):
def __init__(self, name, age, phone_number):
super().__init__(name, age) # ① 親の __init__ で name / age をセット
self.phone_number = phone_number # ② 子だけの属性を追加
def show(self):
print(f"{self.name} / {self.age} / {self.phone_number}")
Employee("田中", 45, "080-1111-2222").show()
# 田中 / 45 / 080-1111-2222
super().__init__(...) を呼び忘れると属性が消える
__init__をオーバーライドしたのにsuper().__init__(...) を呼ばなかった場合、親側で行われていたself.name = nameなどの代入が実行されないままになります。その後でself.nameを読みに行くとAttributeErrorになるので、__init__を子クラスで書き直すときは真っ先に super().__init__(...) を呼ぶようにしましょう。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2メソッドのオーバーライドに関する説明として、最も適切なものはどれですか?
Q3子クラスの__init__でsuper().__init__(...)を書き忘れた場合、何が起きますか?