Q1ポリモーフィズム の説明として最も適切なものはどれですか?
ポリモーフィズム — 同じ名前で型ごとに違う動きをさせる
Python のポリモーフィズムを解説します。親クラスのメソッドを子クラスでオーバーライドして、呼び出し側は型を意識せず使える設計、if type(...) の分岐を消すパターンまで図解で押さえます。
前回までで継承と MRO の仕組みを押さえました。仕上げとして、オブジェクト指向の三大要素のひとつ — ポリモーフィズム(polymorphism) を整理します。
ポリモーフィズムとは
ポリモーフィズム(polymorphism)は 「同じインターフェース(メソッド名)で、型ごとに違う動きをする」 という考え方です。たとえば「給与を計算する」という処理を、従業員(Employee) ・ マネージャー(Manager) ・ エンジニア(Engineer) のそれぞれで違う計算式にしたい、というケースを考えます。
親クラス Employee に calculate_salary メソッドを置いて、子クラス Manager と Engineer は同じ名前のメソッドを 自分用の計算式でオーバーライド します。こうしておくと、呼び出す側は 「どのクラスかを気にせず employee.calculate_salary() と書くだけ」 で、それぞれに合った計算が実行されます。
Employee に calculate_salary を定義 → 子クラス Manager / Engineer がそれぞれ自分用の式で 上書き。同じメソッド名でクラスごとに違う計算が走る。親クラスを土台に、子クラスで計算式を変える
実例として給与計算を作ってみます。Employee を親にして、Manager(チームサイズで上乗せ)と Engineer(スキルレベルで上乗せ)を子クラスとして定義します。各クラスが 同じ名前 calculate_salary を持っているのがポイントです。
class Employee:
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def calculate_salary(self): # 一般従業員 = 基本給だけ
return self.base_salary
class Manager(Employee):
def __init__(self, name, base_salary, team_size):
super().__init__(name, base_salary)
self.team_size = team_size
def calculate_salary(self): # チームサイズに応じて加算
return self.base_salary + self.team_size * 50000
class Engineer(Employee):
def __init__(self, name, base_salary, skill_level):
super().__init__(name, base_salary)
self.skill_level = skill_level
def calculate_salary(self): # スキルレベルに応じて加算
return self.base_salary + self.skill_level * 20000
emp がどのクラスかに応じて 自動で違うメソッドが選ばれる — これがポリモーフィズムの力。リストでまとめて扱う — 「型を気にしないループ」
ポリモーフィズムが本領を発揮するのは、異なる型のオブジェクトを 1 つのリストに詰めてまとめて処理する ような場面です。給与計算システムを PayrollSystem クラスとしてまとめると、ループの中身がきれいに 1 行で済みます。
class PayrollSystem:
def __init__(self):
self.employees = []
def add(self, employee):
self.employees.append(employee)
def total(self):
result = 0
for emp in self.employees: # 型はバラバラでよい
result += emp.calculate_salary() # 同じメソッド名で OK
return result
payroll = PayrollSystem()
payroll.add(Employee("田中", 300000))
payroll.add(Manager("佐藤", 800000, 8))
payroll.add(Engineer("鈴木", 300000, 4))
print(payroll.total()) # 1880000
employees = [...](種類が混ざっている)for emp in self.employees:で順に処理- emp.calculate_salary() を呼ぶだけ
- base_salary を返す
- base + team * 5万
- base + skill * 2万
PayrollSystem 側のコードは 「同じメソッド名で呼ぶ」 だけで型ごとに正しい計算が走る。ポリモーフィズムを使わなかった場合の比較
もしポリモーフィズムを使わずに同じことを書こうとすると、if type(emp) == ...: で型を見て分岐する ような書き方になります。これは動きはするものの、新しい職種を追加するたびに if を 1 段増やす 必要があり、忘れた瞬間にバグが生まれます。
# ❌ ポリモーフィズムを使わない(型で分岐する)書き方
def calc(emp):
if type(emp) is Manager:
return emp.base_salary + emp.team_size * 50000
elif type(emp) is Engineer:
return emp.base_salary + emp.skill_level * 20000
else:
return emp.base_salary
# ✅ ポリモーフィズム版(クラス側に処理を寄せる)
def calc(emp):
return emp.calculate_salary() # 1 行で済む
if type が並び、新しい型が増えるたびに分岐を増やす必要がある。ポリモーフィズム型 は呼び出し側を 1 行に保ったまま、新しい型はクラスを足すだけで対応できる。「呼び出し側は型を気にしない」が合言葉
ポリモーフィズムが効いている設計かどうかは、呼び出し側のコードに if type(...) や if isinstance(...) が並んでいないか で判断できます。並んでしまっているなら、その分岐をクラス側のメソッドオーバーライドに引っ越す のがリファクタリングの定石です。「クラスを増やす」と「if を増やす」は、多くの場合トレードオフ になります。
ダックタイピング — 同じメソッド名さえあればよい
Python のポリモーフィズムには、もう一段ゆるい考え方があります。それが ダックタイピング(duck typing) です。「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」という考え方を、Python では 「同じメソッド名さえ持っていればクラスは何でもよい」 という形で取り入れています。
たとえば下のコードでは、Cat と Dog は どちらも Animal を継承していなくても、speak() というメソッドさえ持っていれば同じ関数で扱えます。継承関係よりも 「持っているメソッド」が一致しているか を優先する — これが Python らしいポリモーフィズムです。
class Cat:
def speak(self):
return "ニャー"
class Dog:
def speak(self):
return "ワン"
def shout(animal): # 引数の型は問わない
print(animal.speak())
shout(Cat()) # ニャー
shout(Dog()) # ワン
Java や C# のように 「親クラスをそろえないとポリモーフィズムが効かない」 タイプの言語と違い、Python は呼び出した瞬間にメソッドがあれば動きます。設計の自由度は上がりますが、「同じ意味でメソッド名をそろえる」 という規律は呼び出し側の責任になる — その点だけ意識しておきましょう。
2 つの設計を 1 枚にまとめると
- 呼び出し側のコード —
emp.calculate_salary()の 1 行 - 新しい型を追加 — 新クラスで
calculate_salaryを実装するだけ - 変更の影響範囲 — クラス側に閉じる
- 可読性 — 「同じメソッド名で型ごとに違う」と読めば済む
- 呼び出し側のコード —
if type(emp) is ...の連発 - 新しい型を追加 — 全分岐を見直す必要
- 変更の影響範囲 — 呼び出し側にも波及
- 可読性 — 分岐ロジックを毎回読む必要
理解度チェック
まずは1問ずつ答えてみましょう。
Q2ポリモーフィズムを使うと、呼び出し側のコードからどんな構造が消える ことが多いですか?
Q3Python の ダックタイピング に関する説明として最も適切なものはどれですか?