Q1次のクラス定義のうち、Dog が Animal を継承する正しい書き方はどれですか?
クラスの継承 — 既存クラスの機能を引き継ぐ
Python のクラスの継承を解説します。class 子(親): で親の属性とメソッドを引き継ぐ書き方、メソッドの上書き(オーバーライド)、super() で親のメソッドを呼ぶ流れを図解で押さえます。
前回は __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__(...) を 書き忘れた 場合、何が起きますか?