- Python
Python 抽象クラスとProtocol|ABCとの違い・使い分け早見表
Pythonの抽象クラスとは、abc.ABC を継承し @abstractmethod を付けたメソッドを持つクラスで、「子クラスで必ず実装すべきメソッド」を決めるための仕組みです。一方 typing.Protocol は、継承しなくても「同じメソッドを持っていれば同じ型とみなす」ための型ヒントの仕組みです。
ABCは 実行時に(インスタンスを作る瞬間に)実装漏れを TypeError で知らせ、Protocolは mypy や pyright などの型チェッカーで実行前に 知らせます。この記事では、通常の継承・ABC・Protocol・@runtime_checkable・ダックタイピングの5つを 1つの比較表 にまとめ、Python 3.11.15 と 3.14.7 の実行結果、mypy 1.20.2 と pyright 1.1.414 の実際のエラー出力 で違いを確認します。目的から選べる判断表、多重継承と super() の注意点、collections.abc の抽象基底クラス一覧(必要なメソッドを実行して取得)までまとめました。
型ヒント全般や dataclass の書き方は別記事に譲り、ここでは 「インターフェース(約束ごと)をどう表すか」 に絞って解説します。
ABCとProtocolの違い早見表
5つの方法の比較表
| 観点 | 通常の継承 | ABC(抽象クラス) | Protocol | @runtime_checkable Protocol |
ダックタイピング(型なし) |
|---|---|---|---|---|---|
| 書き方 | class B(A) |
class A(ABC) + @abstractmethod |
class P(Protocol) |
@runtime_checkable class P(Protocol) |
何も書かない |
| 実装側の継承 | 必要 | 必要(または register) |
不要 | 不要 | 不要 |
| 実装漏れに気づくタイミング | 呼んだとき | インスタンス生成時に TypeError | 型チェック時(mypy/pyright) | 型チェック時 | 呼んだときに AttributeError |
isinstance で判定 |
できる | できる | TypeError | できる(メソッドの有無だけ) | ― |
| 引数・戻り値の型の照合 | しない | しない | する(型チェッカー) | 実行時はしない | しない |
| 共通処理(既定実装)を持てる | はい | はい | 明示的に継承した場合のみ | 同左 | ― |
| 既存クラス・外部ライブラリに後から当てる | 難しい | register で可(検査なし) |
そのまま当てられる | そのまま当てられる | ― |
| 主な用途 | 実装の再利用 | 継承前提の枠組み・プラグイン基底 | 引数の型・依存の差し替え | 実行時の分岐も必要なとき | 小さなスクリプト |
目的別の選び方
| やりたいこと | 選ぶもの | 理由 |
|---|---|---|
| 子クラスに必ずメソッドを実装させたい | ABC | 実装漏れはインスタンス生成時に TypeError |
| 共通の処理を親に書き、一部だけ子に任せたい | ABC(テンプレートメソッド) | 抽象メソッドと通常メソッドを混在できる |
| 既存クラスや外部ライブラリのクラスに、後から型を当てたい | Protocol | 相手のコードを変えずに済む |
| 関数の引数に「このメソッドがあればよい」と書きたい | Protocol | 構造で判定(構造的部分型) |
| プラグインを基底クラスで登録・列挙したい | ABC | __subclasses__() や isinstance で扱える |
| テスト用の偽物(フェイク)を差し込みたい | Protocol + フェイククラス/ABC なら Mock(spec=...) |
継承なしで型チェックが通る |
| 実行時に「〜を持っているか」で分岐したい | @runtime_checkable Protocol |
ただし名前の有無しか見ない |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python 抽象クラスとProtocol|ABCとの違い・使い分け早見表」
ABC(抽象クラス)の書き方と実行結果
抽象メソッドと抽象プロパティを定義する
ABC を継承し、実装を子クラスに任せたいメソッドに @abstractmethod を付けます。 プロパティを抽象にするときは @property の内側(下)に @abstractmethod を書きます。
import sys
from abc import ABC, abstractmethod
print(sys.version.split()[0])
class Exporter(ABC):
@abstractmethod
def export(self, rows: list[dict]) -> str: ...
@property
@abstractmethod
def extension(self) -> str: ...
def filename(self, base: str) -> str:
return f"{base}.{self.extension}"
try:
Exporter()
except TypeError as e:
print("TypeError:", e)
class CsvExporter(Exporter):
def export(self, rows):
return "\n".join(",".join(map(str, r.values())) for r in rows)
try:
CsvExporter()
except TypeError as e:
print("TypeError:", e)
class CsvExporter2(CsvExporter):
extension = "csv"
e = CsvExporter2()
print(e.filename("orders"), repr(e.export([{"id": 1, "qty": 3}, {"id": 2, "qty": 5}])))
print(sorted(Exporter.__abstractmethods__), sorted(CsvExporter.__abstractmethods__), sorted(CsvExporter2.__abstractmethods__))
print(isinstance(e, Exporter))
実行結果(Python 3.11.15):
3.11.15
TypeError: Can't instantiate abstract class Exporter with abstract methods export, extension
TypeError: Can't instantiate abstract class CsvExporter with abstract method extension
orders.csv '1,3\n2,5'
['export', 'extension'] ['extension'] []
True
実行結果(Python 3.14.7):
3.14.7
TypeError: Can't instantiate abstract class Exporter without an implementation for abstract methods 'export', 'extension'
TypeError: Can't instantiate abstract class CsvExporter without an implementation for abstract method 'extension'
orders.csv '1,3\n2,5'
['export', 'extension'] ['extension'] []
True
- 抽象メソッドが1つでも残っていると、そのクラスはインスタンスを作れません。 エラーには残っているメソッド名が出ます(3.12以降は「without an implementation for ...」という文面です)。
- 抽象プロパティは、子クラスでクラス属性
extension = "csv"を置くだけでも満たせます。 名前が定義されれば抽象ではなくなるためです。 __abstractmethods__で、まだ実装されていないメソッド名を確認できます。 デバッグに便利です。filename()のような通常メソッドは親に書けます。 共通処理は親、差分は子という「テンプレートメソッド」の形です。
abstractproperty の代わりに @property + @abstractmethod
abc.abstractproperty は Python 3.3 から非推奨で、代わりに @property と @abstractmethod を重ねます。 クラスメソッドや静的メソッドも同じように重ねられ、@abstractmethod は一番内側(関数のすぐ上)に書きます。
| 抽象にしたいもの | 書き方(上から順に) | 非推奨の書き方 |
|---|---|---|
| プロパティ | @property → @abstractmethod |
@abstractproperty |
| クラスメソッド | @classmethod → @abstractmethod |
@abstractclassmethod |
| 静的メソッド | @staticmethod → @abstractmethod |
@abstractstaticmethod |
from abc import ABC, abstractmethod
class Base(ABC):
@classmethod
@abstractmethod
def create(cls): ...
@staticmethod
@abstractmethod
def version() -> str: ...
@abstractmethod
def hello(self):
return "Base.hello"
class Impl(Base):
@classmethod
def create(cls):
return cls()
@staticmethod
def version():
return "1.0"
def hello(self):
return "Impl + " + super().hello()
print(Impl.create().hello(), Impl.version())
Impl + Base.hello 1.0
抽象メソッドにも中身を書けて、子クラスから super().hello() で呼べます。 「共通の前処理は親に書いておき、必ず子でも上書きさせる」ときに使えます。
ABCを継承しないと @abstractmethod は効かない
from abc import ABC, abstractmethod, update_abstractmethods
class NoABC:
@abstractmethod
def run(self): ...
print("NoABC():", type(NoABC()).__name__)
class Late(ABC):
pass
def m(self): ...
Late.m = abstractmethod(m)
print("Late():", type(Late()).__name__)
update_abstractmethods(Late)
try:
Late()
except TypeError as e:
print("TypeError:", e)
実行結果(Python 3.11.15):
NoABC(): NoABC
Late(): Late
TypeError: Can't instantiate abstract class Late with abstract method m
@abstractmethod はメタクラス ABCMeta(ABC を継承すると付く)と組み合わせたときだけ効きます。 また、クラスを作ったあとに抽象メソッドを追加しても反映されず、abc.update_abstractmethods()(3.10+)を呼ぶと反映されます。
register で既存クラスを「仮想サブクラス」にする
from abc import ABC, abstractmethod
class Sized2(ABC):
@abstractmethod
def __len__(self): ...
Sized2.register(list)
print(isinstance([1], Sized2), issubclass(list, Sized2), list.__mro__)
True True (<class 'list'>, <class 'object'>)
register すると、継承していないクラスも isinstance で True になります。 ただしメソッドを持っているかの検査はなく、MRO(メソッドの探索順)にも ABC は入らないので、親の通常メソッドは使えません。
Protocolの書き方と実行結果
継承しなくても同じメソッドを持てば OK
Protocol は「send(to, message) -> bool を持つもの」のように、形だけを約束します。 実装側のクラスは Protocol を継承する必要がありません。
import sys
from typing import Protocol
print(sys.version.split()[0])
class Notifier(Protocol):
def send(self, to: str, message: str) -> bool: ...
class MailNotifier:
def send(self, to: str, message: str) -> bool:
print(f"[mail] {to}: {message}")
return True
class ChatNotifier:
def send(self, to: str, message: str) -> bool:
print(f"[chat] @{to} {message}")
return True
class Printer:
def print_out(self, text: str) -> None:
print(text)
def notify_all(n: Notifier, members: list[str]) -> int:
return sum(n.send(m, "在庫が入荷しました") for m in members)
print(notify_all(MailNotifier(), ["a@example.com"]))
print(notify_all(ChatNotifier(), ["sato", "kato"]))
try:
notify_all(Printer(), ["x"])
except AttributeError as e:
print("AttributeError:", e)
try:
Notifier()
except TypeError as e:
print("TypeError:", e)
try:
isinstance(MailNotifier(), Notifier)
except TypeError as e:
print("TypeError:", e)
実行結果(Python 3.11.15。3.14.7でも1行目以外は同じ):
3.11.15
[mail] a@example.com: 在庫が入荷しました
1
[chat] @sato 在庫が入荷しました
[chat] @kato 在庫が入荷しました
2
AttributeError: 'Printer' object has no attribute 'send'
TypeError: Protocols cannot be instantiated
TypeError: Instance and class checks can only be used with @runtime_checkable protocols
実行時には Protocol は何も検査しないため、Printer を渡しても send を呼ぶ瞬間まで気づけません(AttributeError)。Protocol の効果は、次の型チェッカーで発揮されます。Protocol 自体はインスタンス化できず、@runtime_checkable なしで isinstance に使うと TypeError です。
mypy・pyright で実装漏れを検出する
Protocol の違反は mypy や pyright で実行前に見つけます。 次のファイルを shapes.py として保存し、チェックします。ABC の実装漏れも型チェッカーが指摘してくれます。
from abc import ABC, abstractmethod
from typing import Protocol
class Notifier(Protocol):
def send(self, to: str, message: str) -> bool: ...
class MailNotifier:
def send(self, to: str, message: str) -> bool:
return True
class Printer:
def print_out(self, text: str) -> None: ...
class LooseNotifier:
def send(self, to: str) -> None: ...
def notify(n: Notifier) -> bool:
return n.send("sato", "入荷しました")
notify(MailNotifier())
notify(Printer())
notify(LooseNotifier())
class Exporter(ABC):
@abstractmethod
def export(self) -> str: ...
class JsonExporter(Exporter):
pass
JsonExporter()
このファイルを python3 shapes.py で実行すると、27行目の notify(Printer()) で AttributeError になるまで何も起きません。型チェッカーなら実行せずに3か所すべてを指摘します。
pip install mypy
mypy shapes.py
mypy 1.20.2 の出力:
shapes.py:27: error: Argument 1 to "notify" has incompatible type "Printer"; expected "Notifier" [arg-type]
shapes.py:28: error: Argument 1 to "notify" has incompatible type "LooseNotifier"; expected "Notifier" [arg-type]
shapes.py:28: note: Following member(s) of "LooseNotifier" have conflicts:
shapes.py:28: note: Expected:
shapes.py:28: note: def send(self, to: str, message: str) -> bool
shapes.py:28: note: Got:
shapes.py:28: note: def send(self, to: str) -> None
shapes.py:40: error: Cannot instantiate abstract class "JsonExporter" with abstract attribute "export" [abstract]
Found 3 errors in 1 file (checked 1 source file)
npx -y pyright shapes.py
pyright 1.1.414 の出力(パスを短く省略):
shapes.py:27:8 - error: Argument of type "Printer" cannot be assigned to parameter "n" of type "Notifier" in function "notify"
"Printer" is incompatible with protocol "Notifier"
"send" is not present (reportArgumentType)
shapes.py:28:8 - error: Argument of type "LooseNotifier" cannot be assigned to parameter "n" of type "Notifier" in function "notify"
"LooseNotifier" is incompatible with protocol "Notifier"
"send" is an incompatible type
Type "(to: str) -> None" is not assignable to type "(to: str, message: str) -> bool"
Function accepts too few positional parameters; expected 2 but received 1
Function return type "None" is incompatible with type "bool" (reportArgumentType)
shapes.py:40:1 - error: Cannot instantiate abstract class "JsonExporter"
"Exporter.export" is not implemented (reportAbstractUsage)
3 errors, 0 warnings, 0 informations
| 違反 | 実行時(python) | mypy | pyright |
|---|---|---|---|
メソッドがない(Printer) |
呼んだときに AttributeError | arg-type エラー |
"send" is not present |
引数・戻り値が違う(LooseNotifier) |
呼び方次第で TypeError | 期待と実際のシグネチャを表示 | 足りない引数・戻り値の型まで表示 |
ABC の実装漏れ(JsonExporter()) |
インスタンス生成時に TypeError | abstract エラー |
reportAbstractUsage |
Protocol は引数の数や戻り値の型まで照合してくれます。 ここが、次の @runtime_checkable との大きな違いです。
runtime_checkable はメソッドの有無しか見ない
@runtime_checkable を付けた Protocol は isinstance で使えますが、確認するのは「その名前の属性があるか」だけです。 引数や戻り値の型、呼び出せるかどうかまでは見ません。
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closable(Protocol):
def close(self) -> None: ...
class File:
def close(self) -> None: ...
class WrongSig:
def close(self, force: bool, reason: str) -> int:
return 0
class AttrOnly:
close = 123
print(isinstance(File(), Closable), isinstance(WrongSig(), Closable), isinstance(AttrOnly(), Closable), isinstance(object(), Closable))
print(issubclass(File, Closable))
@runtime_checkable
class HasName(Protocol):
name: str
class User:
def __init__(self):
self.name = "sato"
print(isinstance(User(), HasName))
try:
issubclass(User, HasName)
except TypeError as e:
print("TypeError:", e)
実行結果(Python 3.11.15):
True True True False
True
True
TypeError: Protocols with non-method members don't support issubclass()
Python 3.14.7 では最後の行が次のように、対象の属性名まで表示されます。
TypeError: Protocols with non-method members don't support issubclass(). Non-method members: 'name'.
引数の違う WrongSig も、ただの数値 close = 123 を持つ AttrOnly も True になりました。 実行時の isinstance は「入口の振り分け」程度に使い、正しさは型チェッカーで確かめるのが安心です。データ属性を持つ Protocol は issubclass が使えない点も覚えておきましょう。
Protocol を明示的に継承して既定実装を使う
from typing import Protocol
class Greeter(Protocol):
def name(self) -> str: ...
def greet(self) -> str:
return f"こんにちは、{self.name()}さん"
class Member(Greeter):
def name(self) -> str:
return "佐藤"
print(Member().greet())
class Half(Greeter):
pass
print("Half():", type(Half()).__name__)
こんにちは、佐藤さん
Half(): Half
Protocol を明示的に継承すると、Protocol に書いた通常メソッド(greet)を既定実装として使えます。 ただし実装漏れ(Half)は実行時には止まらないので、実行時にも確実に止めたいなら ABC を使います。mypy 1.20.2 では次のように検出されます(ファイル名 greeter.py)。
greeter.py:18: error: Cannot instantiate abstract class "Half" with abstract attribute "name" [abstract]
Python 3.13以降の補助関数
import typing
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closable(Protocol):
def close(self) -> None: ...
class File:
def close(self) -> None: ...
print(typing.get_protocol_members(Closable), typing.is_protocol(Closable), typing.is_protocol(File))
実行結果(Python 3.14.7):
frozenset({'close'}) True False
typing.get_protocol_members() と typing.is_protocol() は 3.13 で追加された関数で、Protocol のメンバー一覧や、Protocol かどうかを調べられます。
多重継承と super() の注意点
Mixin と ABC を組み合わせるときは super() を協調させる
多重継承では、メソッドは MRO(Method Resolution Order)の順に探されます。 各クラスの __init__ で super().__init__(**kw) を呼ぶと、MRO の全クラスの初期化が1回ずつ実行されます。
from abc import ABC, abstractmethod
class Storage(ABC):
def __init__(self, name: str, **kw):
super().__init__(**kw)
self.name = name
print("Storage.__init__")
@abstractmethod
def save(self, key: str, data: bytes) -> None: ...
class LoggingMixin:
def __init__(self, **kw):
super().__init__(**kw)
print("LoggingMixin.__init__")
def log(self, msg):
print(f"[{type(self).__name__}] {msg}")
class MemoryStorage(LoggingMixin, Storage):
def __init__(self, name: str):
super().__init__(name=name)
self.items: dict[str, bytes] = {}
def save(self, key, data):
self.items[key] = data
self.log(f"saved {key}")
s = MemoryStorage("mem")
s.save("a", b"1")
print([c.__name__ for c in MemoryStorage.__mro__])
実行結果(Python 3.11.15。3.14.7でも同じ):
Storage.__init__
LoggingMixin.__init__
[MemoryStorage] saved a
['MemoryStorage', 'LoggingMixin', 'Storage', 'ABC', 'object']
- MRO は
MemoryStorage → LoggingMixin → Storage → ABC → objectの順です。LoggingMixinのsuper()は、親クラスではないStorageを指します。 printがsuper()の後にあるため、表示は MRO の逆順(奥から)になります。- 引数はキーワード(
name=name)と**kwで受け渡すと、どの順に並んでも壊れにくくなります。 - Mixin は ABC より左に書くのが一般的です(Mixin のメソッドを優先させるため)。
別のメタクラスを持つクラスと ABC を混ぜる
from abc import ABC, ABCMeta
class Meta(type):
pass
class WithMeta(metaclass=Meta):
pass
try:
class Both(WithMeta, ABC):
pass
except TypeError as e:
print("TypeError:", e)
class MetaABC(Meta, ABCMeta):
pass
class Both2(WithMeta, ABC, metaclass=MetaABC):
pass
print(type(Both2).__name__)
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
MetaABC
ABC のメタクラスは ABCMeta なので、独自メタクラスを持つクラスと混ぜると metaclass conflict になります。 両方を継承したメタクラスを作って指定すると解決します。Protocol で済む場面なら、継承自体が不要なのでこの問題は起きません。
テストのモック・フェイクと組み合わせる
ABC には Mock(spec=...) や create_autospec が、Protocol には「継承しないフェイククラス」が相性のよい組み合わせです。
from abc import ABC, abstractmethod
from typing import Protocol
from unittest.mock import Mock, create_autospec
class Storage(ABC):
@abstractmethod
def save(self, key: str, data: bytes) -> None: ...
m = Mock(spec=Storage)
print(isinstance(m, Storage))
m.save("k", b"x")
m.save.assert_called_once_with("k", b"x")
try:
m.load("k")
except AttributeError as e:
print("AttributeError:", e)
auto = create_autospec(Storage, instance=True)
try:
auto.save("k")
except TypeError as e:
print("TypeError:", e)
class Clock(Protocol):
def now(self) -> str: ...
class FixedClock:
def now(self) -> str:
return "2026-10-01T09:00"
def stamp(c: Clock, text: str) -> str:
return f"{c.now()} {text}"
print(stamp(FixedClock(), "棚卸し開始"))
実行結果(Python 3.11.15。3.14.7でも同じ):
True
AttributeError: Mock object has no attribute 'load'
TypeError: missing a required argument: 'data'
2026-10-01T09:00 棚卸し開始
| テストの道具 | ABC と | Protocol と |
|---|---|---|
Mock(spec=X) |
isinstance も True、存在しない属性は AttributeError |
使えるが、型チェッカー上は Mock 型 |
create_autospec(X, instance=True) |
引数の数まで検査 | 同様に使える |
| フェイククラス(自作) | ABC の継承が必要 | 継承なしで mypy が通る |
時刻・外部通知・保存先など、テストで差し替えたい依存は Protocol で受け取ると、本物とフェイクを自由に入れ替えられます。
collections.abc の抽象基底クラス一覧
collections.abc には、リストや辞書のような「コンテナの形」を表す抽象基底クラスが用意されています。 次の表の「抽象メソッド」は、各クラスの __abstractmethods__ を実際に出力して作りました(Python 3.11.15。Buffer は 3.12+ のため 3.14.7 で確認)。
| クラス | 抽象メソッド(自分で書くもの) | 継承するともらえる主なメソッド | 当てはまる組み込み型の例 |
|---|---|---|---|
Container |
__contains__ |
― | list、str、dict |
Hashable |
__hash__ |
― | tuple、str(list は対象外) |
Iterable |
__iter__ |
― | list、dict、set、ジェネレータ |
Iterator |
__next__ |
__iter__ |
iter([]) の戻り値 |
Reversible |
__iter__、__reversed__ |
― | list、dict |
Generator |
send、throw |
close、__iter__、__next__ |
ジェネレータ |
Sized |
__len__ |
― | list、dict、str |
Callable |
__call__ |
― | 関数、クラス |
Collection |
__contains__、__iter__、__len__ |
― | list、set、dict |
Sequence |
__getitem__、__len__ |
__contains__、__iter__、__reversed__、index、count |
list、tuple、str、range |
MutableSequence |
__getitem__、__setitem__、__delitem__、__len__、insert |
append、extend、pop、remove、reverse など |
list |
Set |
__contains__、__iter__、__len__ |
<=、&、|、-、isdisjoint など |
frozenset、set |
MutableSet |
__contains__、__iter__、__len__、add、discard |
remove、pop、clear、|= など |
set |
Mapping |
__getitem__、__iter__、__len__ |
get、keys、items、values、__contains__、== |
dict |
MutableMapping |
__getitem__、__setitem__、__delitem__、__iter__、__len__ |
pop、popitem、clear、update、setdefault |
dict |
Awaitable |
__await__ |
― | コルーチン |
AsyncIterable |
__aiter__ |
― | 非同期ジェネレータ |
AsyncIterator |
__anext__ |
__aiter__ |
非同期ジェネレータ |
Buffer(3.12+) |
__buffer__ |
― | bytes、bytearray |
Mapping を継承して辞書のように使えるクラスを作る
from collections.abc import Mapping, Iterable, Sequence, Sized, Callable, Hashable
class Inventory(Mapping):
def __init__(self, data):
self._d = dict(data)
def __getitem__(self, k):
return self._d[k]
def __iter__(self):
return iter(self._d)
def __len__(self):
return len(self._d)
inv = Inventory({"pen": 12, "note": 5})
print(inv.get("pen"), inv.get("x", 0), list(inv.keys()), list(inv.items()), "pen" in inv, inv == {"pen": 12, "note": 5})
print(isinstance(inv, Mapping), isinstance(inv, dict))
print(isinstance([1], Iterable), isinstance("ab", Sequence), isinstance({1}, Sequence), isinstance(len, Callable), isinstance([], Hashable), isinstance((1,), Hashable))
class Bag:
def __len__(self):
return 3
print(isinstance(Bag(), Sized), isinstance(Bag(), Iterable))
class Half(Mapping):
def __getitem__(self, k): ...
try:
Half()
except TypeError as e:
print("TypeError:", e)
実行結果(Python 3.11.15):
12 0 ['pen', 'note'] [('pen', 12), ('note', 5)] True True
True False
True True False True False True
True False
TypeError: Can't instantiate abstract class Half with abstract methods __iter__, __len__
- 3つのメソッドを書くだけで、
get・keys・items・in・==がそろいました。 読み取り専用の辞書風クラスを作るときに便利です。 Bagは継承していないのにisinstance(Bag(), Sized)が True です。SizedやIterableなどの単純な ABC は__subclasshook__で「メソッドを持っているか」を見ています。- 型ヒントでも
collections.abcを使えます。 引数をlist[str]ではなくIterable[str]にすると、リスト・タプル・ジェネレータのどれでも受け付けられます。
よくある質問
Q. 抽象クラスの実装漏れはいつエラーになりますか?
A. ABC ではインスタンスを作った瞬間に TypeError になります(Can't instantiate abstract class ...)。メソッドを呼ぶまで待たずに気づけます。Protocol は実行時には検査しないので、mypy や pyright で確認します。
Q. ABC と Protocol はどちらを使えばよいですか? A. 実装クラスに継承させて共通処理を持たせたいなら ABC、既存クラスや外部のクラスにも当てたい・テストで差し替えたいなら Protocol が向いています。両方を組み合わせ、引数の型は Protocol、実装の土台は ABC という使い方もできます。
Q. abstractproperty はまだ使えますか?
A. Python 3.14 にも残っていますが、3.3 から非推奨です。 @property の下に @abstractmethod を重ねて書きます。
Q. runtime_checkable を付ければ実行時に型を保証できますか? A. 保証できるのは「その名前の属性があるか」までです。 実行結果のとおり、引数が違うメソッドや数値の属性でも True になります。引数や戻り値の照合は mypy・pyright で行います。
まとめ
- ABC は「継承させて実装を強制する」仕組み。実装漏れはインスタンス生成時に TypeError
- 抽象プロパティは
@property+@abstractmethod。abstractpropertyは非推奨 - Protocol は「継承なしで形が合えば OK」。違反は mypy・pyright が引数や戻り値まで照合して指摘
@runtime_checkableのisinstanceは名前の有無だけを見る。正しさは型チェッカーで確認- 多重継承は MRO の順に
super()がつながる。**kwで受け渡し、Mixin を左に書く collections.abcを継承すると、少ない抽象メソッドでget・itemsなどがそろう
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python 抽象クラスとProtocol|ABCとの違い・使い分け早見表」
株式会社RJCでは、Pythonの抽象クラスとProtocolのような設計の考え方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。