Runtime Checkable Protocols
メニューを表示するにはスワイプしてください
By default, a Protocol is a static typing construct – it exists for type checkers like mypy but has no runtime presence. @runtime_checkable changes this, enabling isinstance() checks at runtime. Understanding exactly what this check does and does not verify is essential for using it safely.
What @runtime_checkable Enables
Without the decorator, calling isinstance() on a Protocol raises TypeError. With it, the check passes if the object has all the required methods as attributes:
12345678910111213141516171819202122232425from typing import Protocol, runtime_checkable @runtime_checkable class Cacheable(Protocol): def cache_key(self) -> str: ... def invalidate(self) -> None: ... class UserSession: def __init__(self, session_id): self.session_id = session_id def cache_key(self): return f"session:{self.session_id}" def invalidate(self): print(f"Invalidating {self.session_id}") class EmptyClass: pass print(isinstance(UserSession("s-001"), Cacheable)) # True – both methods present print(isinstance(EmptyClass(), Cacheable)) # False – methods missing
The Limitation: Signatures Are Not Checked
@runtime_checkable only checks that the names exist as attributes – it does not verify argument counts, return types, or whether the attribute is actually callable:
123456789101112131415161718from typing import Protocol, runtime_checkable @runtime_checkable class Processable(Protocol): def process(self, data: list) -> dict: ... # Wrong signature – but isinstance() still passes class BrokenProcessor: def process(self): # Missing the data argument return "not a dict" # Not even callable – but isinstance() still passes class FakeProcessor: process = "not a function" # A string, not a method print(isinstance(BrokenProcessor(), Processable)) # True – name exists print(isinstance(FakeProcessor(), Processable)) # True – name exists
This is why @runtime_checkable is a structural check by name, not a full contract verification. Use it for quick guards, not for guaranteeing correct behavior.
Protocol Attributes
Protocols can also require data attributes, not just methods. @runtime_checkable checks for their presence the same way:
1234567891011121314151617181920from typing import Protocol, runtime_checkable @runtime_checkable class HasMetadata(Protocol): version: str author: str class ReportTemplate: version = "2.0" author = "analytics-team" def render(self): return f"Report v{self.version} by {self.author}" class IncompleteTemplate: version = "1.0" # Missing author print(isinstance(ReportTemplate(), HasMetadata)) # True – both attributes present print(isinstance(IncompleteTemplate(), HasMetadata)) # False – author missing
Protocol Inheritance
Protocols can inherit from other Protocols to compose larger interfaces:
1234567891011121314151617181920212223242526from typing import Protocol, runtime_checkable @runtime_checkable class Readable(Protocol): def read(self) -> str: ... @runtime_checkable class Writable(Protocol): def write(self, data: str) -> None: ... @runtime_checkable class ReadWritable(Readable, Writable, Protocol): pass # Combines both interfaces class FileBuffer: def read(self): return "buffered content" def write(self, data): print(f"Writing: {data}") print(isinstance(FileBuffer(), Readable)) # True print(isinstance(FileBuffer(), Writable)) # True print(isinstance(FileBuffer(), ReadWritable)) # True – satisfies both
When to Use @runtime_checkable
Use @runtime_checkable when:
- You need a lightweight guard at a function entry point to give a clear error message;
- You are working with dynamically loaded plugins and need to verify basic interface presence;
- You want to filter a list of objects by capability.
Do not rely on it as a complete correctness check – call the methods and let AttributeError or TypeError surface any mismatches naturally.
12345678910111213from typing import Protocol, runtime_checkable @runtime_checkable class Exporter(Protocol): def export(self, data: list) -> str: ... def run_export(exporter, data): if not isinstance(exporter, Exporter): raise TypeError( f"Expected an Exporter, got {type(exporter).__name__}" ) return exporter.export(data)
フィードバックありがとうございます!
AIに質問する
AIに質問する
何でも質問するか、提案された質問の1つを試してチャットを始めてください