class ch03 — Protocol と ABC
テクノロジ系 / L07 class — クラスとデータ構造
class ch03 — Protocol と ABC
「型の約束」を 2 種類使い分ける
ゴール
typing.Protocolで「形が同じなら OK」(構造的部分型)を書けるabc.ABC+@abstractmethodで「継承を強制」する書き方が読める- Protocol と ABC の使い分け基準を 1 行で言える
なぜ「型の約束」が要るのか
def send(notifier, message: str) -> None:
notifier.notify(message)notifierは 何でも 渡せてしまう (動的型付け)- 「
notify(message)を持っている」と 保証 したい - → Protocol / ABC の出番
Protocol: 「形が合えば仲間」
from typing import Protocol
class Notifier(Protocol):
def notify(self, message: str) -> None: ...
class SlackNotifier:
def notify(self, message: str) -> None:
print(f"[Slack] {message}")
def send(n: Notifier, msg: str) -> None:
n.notify(msg)
send(SlackNotifier(), "hi") # OK: 継承していなくても notify があれば OKSlackNotifier は Notifier を継承していない。それでも型チェックを通る。
ABC: 「継承して実装を強制」
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def notify(self, message: str) -> None: ...
class SlackNotifier(Notifier): # 継承が必須
def notify(self, message: str) -> None:
print(f"[Slack] {message}")
# Notifier() # ❌ TypeError: abstract メソッドあり
# 未実装で継承すると、インスタンス化時に落ちる並べて見る
| Protocol | ABC | |
|---|---|---|
| 関係 | 構造的 (duck typing) | 名義的 (継承必須) |
| 既存クラスへの後付け | できる | できない (継承を直す必要あり) |
| 実装漏れの検出 | mypy / pyright 等の 静的 チェック | 実行時 に TypeError |
| 共通実装の共有 | しにくい | しやすい (具象メソッドを持てる) |
使い分け 1 行ルール
- 外部から渡ってくる「形」だけ縛りたい →
Protocol - 自分で抽象クラスを設計し共通実装を持たせたい →
ABC
FastAPI の DI (Depends) や差し替え可能な実装は、ほぼ Protocol で足りる。
drill
drill/ — Notifier Protocol と Slack / Mail 2 種類の実装を完成させる
よくある詰まり
| 症状 | 原因 | 対処 |
|---|---|---|
TypeError: Can't instantiate abstract class | ABC を継承したが @abstractmethod を実装していない | サブクラスでメソッドを定義 |
| Protocol が効いていない感じ | Protocol は実行時には基本ノーチェック | mypy / pyright で静的検査する |
isinstance(x, MyProtocol) で落ちる | @runtime_checkable が無い | @runtime_checkable を付ける |
次に学ぶ領域
| 領域 | 続けて読む |
|---|---|
| dataclass で値オブジェクト | class/ch02-dataclass |
| FastAPI の Depends で差し替え | fastapi/ch05-depends |
| テスト時にダミー実装 | test/ch01-pytest |
力試ししたい方は クイズ形式の演習(任意) もあります。読むだけでも十分です。