Pythonのtyping.Protocol徹底解説と実務的な見解
1. イントロ
Pythonで「インターフェースを表現したい」と思ったことはありませんか?
抽象基底クラス(ABC)は少し重く感じるし、柔軟性に欠ける…。
そんなときに登場するのが typing.Protocol です。
この記事では、
- Protocolの仕組み
- 使える場面と便利さ
- そして実務的には「推奨しない」という著者の立場
をまとめます。
2. 背景
PythonはDuck Typingの文化を持っています。
「アヒルのように歩いて、ガーガー鳴くなら、それはアヒル」。
型ヒントやProtocolは、このDuck Typingを**静的型チェッカー(mypy, pyrightなど)**の世界に持ち込む仕組みです。
注意点は、実行時には何もチェックされないことです。
3. Protocolの最小例
まずは簡単な例を見てみましょう。
from typing import Protocol
class Greeter(Protocol):
def greet(self) -> str: ...
class Person:
def greet(self) -> str:
return "Hello!"
def say_hello(g: Greeter) -> None:
print(g.greet())
say_hello(Person()) # ✅ OK
PersonはGreeterを継承していません。
でもgreet()メソッドを持っているので、Protocolを満たすとみなされます。
これが「構造的部分型(structural subtyping)」です。
4. ABCとの比較
| 比較項目 | Protocol | ABC |
|---|---|---|
| 継承必須 | ❌ 不要(構造さえ揃えばOK) | ✅ 必要 |
| 実行時チェック | ❌ なし | ✅ isinstanceで可能 |
| 柔軟性 | 高い | 低い |
| 適用場面 | 外部ライブラリ/後付けインターフェース | 厳格な契約 |
Protocol = 構造的部分型
ABC = 名目的部分型
5. 実践ユースケース
サービス層 / リポジトリ層
class Storage(Protocol):
def save(self, key: str, data: bytes) -> None: ...
def load(self, key: str) -> bytes: ...
class S3Storage:
def save(self, key: str, data: bytes) -> None: ...
def load(self, key: str) -> bytes: ...
class LocalStorage:
def save(self, key: str, data: bytes) -> None: ...
def load(self, key: str) -> bytes: ...
呼び出し側はStorageを受け取るだけで、S3とローカルを差し替え可能。
テストダブル(モック/スタブ)
class FakeStorage:
def save(self, key: str, data: bytes) -> None: ...
def load(self, key: str) -> bytes: ...
Storageを満たすので、テストで安全に利用できる。
外部ライブラリとの相性
class CsvExportable(Protocol):
def to_csv(self, path: str) -> None: ...
def export(obj: CsvExportable, path: str) -> None:
obj.to_csv(path)
pandas.DataFrameのようにto_csvを持つクラスなら、そのまま利用できる。
6. ベストプラクティス
- 自分で管理するクラス → 明示的にProtocolを継承して「このクラスはこれを実装している」と示す
- 外部ライブラリや既存コード → 継承なしでProtocolを当てはめると便利
- 必ずCIに型チェッカーを導入すること(mypy/pyright)
7. 自身の見解(推奨しない理由)
便利そうに見えるProtocolですが、実務での利用には慎重さが必要です。
-
実行時には何も保証されない
- 型が間違っていても、mypyを通さなければ普通に動いてしまう
-
意図がコードから読み取りづらい
- 継承不要なので「なぜこのクラスがProtocol適合しているのか」が一目で分からない
-
チーム開発では混乱を招く可能性
- 新規参加者がコードを見ても「どのインターフェースを満たす想定か」理解しづらい
-
結局テストに頼るしかない
- 型だけで安全性は担保できない
👉 よって、著者は「基本的には推奨しない」立場です。
ただし、以下のケースでは有効だと考えます。
- テスト用のモック/スタブの型保証
- 外部ライブラリを柔軟にインターフェース化したいとき
8. まとめ
- Protocolは「Pythonにおける型安全なインターフェース」
- 便利だが諸刃の剣
- 実務で使うなら 小さく・限定的に・型チェックとテスト込みで
- 保守性を重視するなら、ABCや明示的な継承の方が無難
💡 練習課題:
あなたのプロジェクトで「Protocolで表せるインターフェース」はありますか?
ぜひ考えてみてください。