「同じ関数で、いろいろなオブジェクトをまとめて扱えたら便利じゃない?」そんなときに力を発揮するのが、オブジェクト指向のポリモーフィズム(多態性)です。
Pythonの学習を始めたばかりでも、同じ名前のメソッドに、クラスごとに違う振る舞いをさせるというイメージさえ掴めれば、すぐに日常のコードで使いこなせます。 ここでは、初心者でも迷わないように、具体例と一緒にわかりやすく解説します。
ポリモーフィズム(多態性)は、異なるクラスのインスタンスが同じメソッド名を持ち、それぞれが「自分らしい」動作をすることを指します。
大事なポイントは2つ。 「同じメソッド名を用意すること」と「中身の実装はクラスごとに違ってよいこと」。 これにより、呼び出し側のコードは何が来ても、この名前のメソッドが呼べるという前提でシンプルに書けます。結果として、コードの柔軟性、再利用性、拡張性がぐっと上がります。
実際のサンプルコードからメリットを体感してみましょう。
以下は、もっとも素直なポリモーフィズムの例です。 共通のメソッド名 speak を用意し、クラスごとに鳴き声が変わるイメージで実装します。
class Animal:
def speak(self):
raise NotImplementedError("このメソッドはサブクラスで実装する必要があります。")
class Dog(Animal):
def speak(self):
return "ワンワン"
class Cat(Animal):
def speak(self):
return "ニャー"
def animal_sound(animal):
print(animal.speak())
# インスタンスを作成
dog = Dog()
cat = Cat()
# ポリモーフィズムを利用
animal_sound(dog) # 出力: ワンワン
animal_sound(cat) # 出力: ニャー
このコードでは、Animal は「動物なら speak を持っているはずだよ」という“契約”だけを示し、具体的な鳴き声はサブクラス側(Dog と Cat)が決めています。
animal_sound 関数は「渡されたものが speak できるなら鳴かせる」という視点だけで書かれていて、Dog でも Cat でも同じ呼び出しで動きます。もし新しく Bird クラスを追加しても、speak を実装するだけで animal_sound は何も変えずに使い続けられます。
「ところで、本当に継承しないとダメ?」と疑問に思った方もいるはず。
Pythonではダックタイピングといって、「アヒルのように鳴くなら、それはアヒルとして扱える」という考え方が強く使われます。
つまり、Animal を継承していなくても、speak メソッドさえあれば同じ関数で扱えるのです。
class Animal:
def speak(self):
raise NotImplementedError("このメソッドはサブクラスで実装する必要があります。")
class Robot:
def speak(self):
return "ピーガガ"
def animal_sound(animal):
print(animal.speak())
robot = Robot()
animal_sound(robot) # 出力: ピーガガ
Robot は Animal を継承していませんが、speak を持っているので問題なく動きます。これが Python のポリモーフィズムの実践的な強み。型や継承関係に縛られすぎず、必要なメソッドがあるかに注目して設計できます。
「でも、型を厳密にチェックしたいときはどうすればいい?」と感じたら、Python 3.8以降で使える「Protocol(プロトコル)」という仕組みが役に立ちます(詳しくは「インターフェースとしてのプロトコル」セクションで解説します)。
必要に応じて型ヒントで“speak を持つもの”を表現でき、静的解析ツールでチェックできます。
現場では「あとから新しい機能を差し替えたい」「別サービスを追加したい」といった場面がよくあります。
ポリモーフィズムを使うと、呼び出し側はインターフェース(メソッド名)だけを信じて書けばよく、個別の違いはクラス側に閉じ込められます。
例えば支払い処理を考えてみましょう。クレジットカード、PayPay、Apple Pay…と対応手段が増えても、呼び出し側は process という同じメソッドを呼ぶだけで済みます。
class PaymentProcessor:
def process(self, amount: int) -> str:
raise NotImplementedError
class CreditCard(PaymentProcessor):
def process(self, amount: int) -> str:
return f"クレジットカードで{amount}円を決済しました"
class PayPay(PaymentProcessor):
def process(self, amount: int) -> str:
return f"PayPayで{amount}円を決済しました"
def checkout(processor: PaymentProcessor, amount: int) -> None:
print(processor.process(amount))
checkout(CreditCard(), 1200)
checkout(PayPay(), 980)
新しい決済手段を追加したいときは、そのクラスに process を実装するだけ。checkout のコードは一切触らなくてよいので、安全に拡張できます。 「呼び出し側は何をするかではなく、どう呼ぶか(メソッド名)だけを知っていればよい」という設計は、テストもしやすく、保守がとても楽になります。
初心者のうちは「どのクラスが親で、どこでオーバーライドして…」と手続きに目が行きがちです。
ですが、本質は「この関数は、渡されたオブジェクトが speak(または process)を持っていると仮定している」という“約束”のほう。つまり、呼び出し側はインターフェースにだけ依存しているのがポイントです。
実装の違いはオブジェクトの中に閉じ込められているため、関数の中身はいつまでもシンプルなまま保てます。新しいクラスを追加しても、関数は変えなくてよい。この気持ちよさを体験すると、ポリモーフィズムを使う理由が腹落ちしてきます。
「本番の処理を呼びたくないけど、振る舞いはテストしたい」そんなときも、ポリモーフィズムが助けてくれます。 共通のメソッド名だけ合わせた“テスト用のダミークラス”を用意すれば、簡単に差し替えできます。
class FakeProcessor:
def process(self, amount: int) -> str:
return f"[TEST] {amount}円を処理しました"
def checkout(processor, amount: int) -> None:
# ダックタイピング:process があればOK
print(processor.process(amount))
checkout(FakeProcessor(), 500)
継承していなくても process を持っていれば動くので、テストが軽く、速く、書きやすくなります。
「最初はちょっと抽象的かも…」と思った方も、同じメソッド名をそろえるだけ、という視点でコードを書いてみると、すぐに実感できます。
ポリモーフィズム(多態性)は、「同じメソッド名で、クラスごとの違いを吸収する」ための基本テクニックです。Pythonではダックタイピングのおかげで、継承していなくても“そのメソッドを持つなら使える”という柔軟さがあります。
この3点を意識するだけで、読みやすく拡張しやすいコードにぐっと近づけます。