【Python】ポリモーフィズムの実現方法

Python におけるポリモーフィズムの実現方法について解説します。
目次
ポリモーフィズム(polymorphism)
クラス実装において、異なるクラスが共通のインターフェース(メソッドやプロパティ)を実装し、同じメソッド名で異なる振る舞いをすることをポリモーフィズム(多様性・多相性)と言います。
Python では、ポリモーフィズムを実現する手法として、以下の 2 つのアプローチがあります。
- 抽象クラスの継承:
abc.ABCによるポリモーフィズム - 構造的部分型:
typing.Protocolによるポリモーフィズム
この記事では、Python でポリモーフィズムを実現する方法を紹介します。
抽象クラスの継承:abc.ABC
以降では、Person、Teacher、Doctor クラスを用いてポリモーフィズムを考えていきます。まずは、継承をベースにした考え方について見ていきましょう。以下は、Person、Teacher、Doctor クラスの継承関係を表した図です。

mywork メソッドは、仕事の内容を出力するメソッドです。しかし、同じ mywork メソッドであっても、Teacher クラスでは教える仕事、Doctor クラスでは診察する仕事であることを説明するように出力の振る舞いを変えたいとします。
このような際にはメソッドのオーバーライドを行います。オーバーライドは「クラスの継承の基本」を参考にしてください。オーバーライドは、任意でメソッドの挙動を変更する仕組みですが、派生クラスでメソッドの実装を強制することまではできません。
派生クラスでのメソッドの実装を強制したい場合は「抽象クラス」「抽象メソッド」を使用します。抽象クラスはインスタンス化できないクラスであり、クラス設計の構造を提供します。抽象メソッドは、抽象クラスを継承する派生クラスが具体的に実装を行う必要があるメソッドのことを言います。
抽象クラスを継承したクラスは、抽象メソッドを実装しない限りインスタンス化できません。そのため、抽象クラスを利用することで、派生クラスに共通のインターフェース(メソッド構造)を強制でき、設計上の一貫性や拡張性を保つことができます。
抽象クラスや抽象メソッドは、ポリモーフィズムを活かしたインターフェース設計に欠かせない概念です。
abc モジュールを用いた抽象クラス・抽象メソッドの実装
Python では、抽象クラスと抽象メソッドを実現するために abc モジュールが使用できます。abc とは「abstract base class」の略です。
以下の例は、abc モジュールを使用して抽象クラス、抽象メソッドを定義しています。
import abc
class Person(abc.ABC):
def __init__(self, name: str) -> None:
self.__name = name
@property
def name(self) -> str:
return self.__name
@name.setter
def name(self, value: str) -> None:
self.__name = value
def say_myname(self) -> None:
print(f"私の名前は、{self.__name}です。")
@abc.abstractmethod
def mywork(self) -> None:
...
# pass でもよい
class Teacher(Person):
def __init__(self, name: str, subject: str) -> None:
super().__init__(name)
self.__subject = subject
@property
def subject(self) -> str:
return self.__subject
@subject.setter
def subject(self, value: str) -> None:
self.__subject = value
def mywork(self) -> None:
print(f"私の仕事は、{self.__subject}の教師です。")
class Doctor(Person):
def __init__(self, name: str, medical_speciality: str) -> None:
super().__init__(name)
self.__medical_speciality = medical_speciality
@property
def medical_speciality(self) -> str:
return self.__medical_speciality
@medical_speciality.setter
def medical_speciality(self, value: str) -> None:
self.__medical_speciality = value
def mywork(self) -> None:
print(f"私の仕事は、{self.__medical_speciality}の医者です。")
def introduction(person: Person) -> None:
"""Person 型の引数を受け取る"""
print("=== introduction ===")
person.say_myname()
person.mywork()
def main():
person1 = Teacher("田中太郎", "英語")
person1.mywork()
introduction(person1)
print("==================================")
person2 = Doctor("鈴木一郎", "内科")
person2.mywork()
introduction(person2)
if __name__ == "__main__":
main()【実行結果】 私の仕事は、英語の教師です。 === introduction === 私の名前は、田中太郎です。 私の仕事は、英語の教師です。 ================================== 私の仕事は、内科の医者です。 === introduction === 私の名前は、鈴木一郎です。 私の仕事は、内科の医者です。
上記例での Personクラスは abc.ABC クラスを継承することで抽象クラスになります。mywork メソッドは @abc.abstractmethod デコレータで付けることで抽象メソッドとなり、これによりサブクラスで必ず実装されることを要求します。
抽象メソッドの定義では、継承したクラス側で実装をするため中身は不要で「...」と記載しておきます。書籍やブログなどでは「pass」と記載している例がありますが、 もちろん pass でも問題ありません。ただし、モダンな Python では「...」と書くのが一般的になってきています。
Teacher クラスと Doctor クラスは Person を継承し、mywork メソッドをオーバーライドします。もし、Doctor クラスで mywork メソッドの実装を忘れると、以下のようなエラーが発生します。
【実行結果例】 Traceback (most recent call last): ...(省略)... TypeError: Can't instantiate abstract class Doctor with abstract methods mywork
このように abc モジュールを活用することで、抽象メソッドの実装を強制し、ポリモーフィズムを実現できます。
抽象クラスにおけるこの特徴は、インターフェース(共通の振る舞い)を定義する際に非常に重要な意味を持ちます。以下の introduction 関数は Person を引数に取る関数です。しかし、呼び出し時には Teacher や Doctor クラスのインスタンスを受け取ることができていることが分かります。
def introduction(person: Person) -> None:
"""Person 型の引数を受け取る"""
print("=== introduction ===")
person.say_myname()
person.mywork()def main():
person1 = Teacher("田中太郎", "英語")
person1.mywork()
introduction(person1)
print("==================================")
person2 = Doctor("鈴木一郎", "内科")
person2.mywork()
introduction(person2)このように「共通の型に基づいて異なるクラスを同じように扱う」ことが可能になる点は、インターフェース設計の中心となる考え方です。これがまさにポリモーフィズムであり、抽象クラスが果たす重要な役割です。
構造的部分型:typing.Protocol
上記では、abc モジュールを用いて抽象クラスを継承し、サブクラスごとにメソッドを実装させることでポリモーフィズムを実現する方法を紹介しました。
近年では、もう 1 つのポリモーフィズムの実現方法として typing.Protocol を使用した方法も広く利用されており、Python における強力なインターフェース手法となっています。
この方法は abc による手法と異なり「クラスの継承が不要」である点が大きな特徴です。まずは、Protocol のベースとなる考え方を説明し、具体的な実装方法を見ていきましょう。
構造的部分型(Structural Subtyping)
まずは、Protocol の基盤となる考え方である「構造的部分型(Structural Subtyping)」について説明します。
abc.ABC を用いた抽象クラスでは、親クラス(抽象クラス)を継承し、抽象メソッドをオーバーライドすることでポリモーフィズムを実現します。一方で、Protocol が採用しているのは構造的部分型という考え方です。
構造的部分型とは「クラス継承をしていなくても、必要なメソッド・プロパティを持っていれば、その型として扱ってよい」という仕組みであり、Python がもともと持っているダックタイピングと非常に相性が良い手法です。
例えば、 Person と Teacher、Doctor の例に当てはめてみると、以下の特徴を持つクラスは継承していなくても Person 型であると扱うことができます。
nameプロパティsay_mynameメソッドmyworkメソッド
typing.Protocol による実装
以下で、具体的な実装例を用いて Protocol の理解を深めましょう。以下は、abc.ABC を用いた方法と出力結果自体は同じですが、背後にある考え方は異なるため説明をしていきます。
from typing import Protocol
# Protocol: 構造的部分型を使ったインターフェース
class Person(Protocol):
@property
def name(self) -> str: ...
@name.setter
def name(self, value: str) -> None: ...
def say_myname(self) -> None: ...
def mywork(self) -> None: ...
# Teacher クラス (Person は継承しない)
class Teacher:
def __init__(self, name: str, subject: str) -> None:
self._name = name
self._subject = subject
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str) -> None:
self._name = value
def say_myname(self) -> None:
print(f"私の名前は、{self._name}です。")
def mywork(self) -> None:
print(f"私の仕事は、{self._subject}の教師です。")
# Doctor クラス (Person は継承しない)
class Doctor:
def __init__(self, name: str, medical_speciality: str) -> None:
self._name = name
self._medical_speciality = medical_speciality
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str) -> None:
self._name = value
def say_myname(self) -> None:
print(f"私の名前は、{self._name}です。")
def mywork(self) -> None:
print(f"私の仕事は、{self._medical_speciality}の医者です。")
def introduction(person: Person) -> None:
"""Person 型の引数を受け取る"""
print("=== introduction ===")
person.say_myname()
person.mywork()
def main() -> None:
person1 = Teacher("田中太郎", "英語")
person1.mywork()
introduction(person1)
print("==================================")
person2 = Doctor("鈴木一郎", "内科")
person2.mywork()
introduction(person2)
if __name__ == "__main__":
main()abc.ABC を用いた方法と異なる点は以下の通りです。
- 継承が不要
TeacherやDoctorがPersonを継承していませんが、必要なメソッドをすべて備えているため、Personの構造的部分型として扱われます。そのため、introduce関数に引数で渡しても問題なく動作します。 - メソッドの実装強制は行われない
abc.ABCでは抽象メソッドが未実装の場合、実行時にエラーが発生しましたが、Protocolでは、実行時に強制しません。 Protocolはインターフェース定義Protocolはインターフェース定義が目的であるため、abc.ABCのように親クラスで共通の具体メソッドを定義し、子クラスで使うことはできません。- 既存クラスにも後付けで適用しやすい
継承していない既存クラスにも、必要なメソッドを追加すれば、Protocolを適用できます。これは、Protocolの大きな利点です。
このように、Protocol で構造的部分型に基づくポリモーフィズムを実現できます。
abc.ABC と typing.Protocol の使い分け
ポリモーフィズムを実現する方法として「abc.ABC を使用する方法」と「typing.Protocol で構造的部分型を使用する方法」を紹介しました。どちらの方法を使用するかは、適用ケースにより十分な検討が必要ですが、使い分けの判断材料となる考え方を紹介します。
継承が必要かどうか
親クラスで共通メソッドを記載したい場合や、子クラスで必ず実装させたいメソッドがある場合は、ABC を使用しましょう。一方で、既存クラスに後付けで「この型として扱いたい」というような場合には、メソッドの定義を追加すればよいだけですので Protocol の使用がおすすめです。
実行時に強制させたいか
ABC では、抽象メソッドを実装していないとインスタンス化の際にエラーとなるため強制力があります。実行時の保証が必要なケースは ABC を使う方が良いでしょう。Protocol の場合は、インスタンス化する際にはチェックはされないため、開発者が十分に注意して実装する必要になります。
複数の型にまたがる後付けのインターフェースが必要
Protocol は、定義したメソッドを実装しているかどうかで判断されます。そのため、ライブラリのクラスや既存コードなどに後付けでインターフェースを追加することで「この Protocol を満たしている」とみなすことができます。これは、ABC では不可能な優れた点です。
設計パターンにより判断する
ABC は、オブジェクト指向ベースの設計思想です。一方で、Protocol は、Haskell や Rust のような型システムを重視したプログラミング言語の設計思想に近い部分があります。設計方針や思想に応じて使い分けるのもよいと思います。
まとめ
Python でのポリモーフィズムの実現方法について解説しました。Python では、ポリモーフィズムを実現する手法として、以下の 2 つのアプローチがあります。
- 抽象クラスの継承:
abc.ABCによるポリモーフィズム - 構造的部分型:
typing.Protocolによるポリモーフィズム
この記事では、各方法の実装例や特徴について紹介しました。ポリモーフィズムはインターフェースの設計などにおいて非常に重要な役割を果たすものであるため、各方法についてしっかりと理解してもらえたらと思います。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。




の基本.jpg)

の使い方.jpg)