Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lære Runtime Checkable Protocols | Protocols vs ABCs
Python Abstract Base Classes

Runtime Checkable Protocols

Stryg for at vise menuen

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:

12345678910111213141516171819202122232425
from 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:

123456789101112131415161718
from 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:

1234567891011121314151617181920
from 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:

1234567891011121314151617181920212223242526
from 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.

12345678910111213
from 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)
question mark

What does isinstance(obj, MyProtocol) verify when MyProtocol is decorated with @runtime_checkable?

Vælg det korrekte svar

Var alt klart?

Hvordan kan vi forbedre det?

Tak for dine kommentarer!

Sektion 3. Kapitel 2

Spørg AI

expand

Spørg AI

ChatGPT

Spørg om hvad som helst eller prøv et af de foreslåede spørgsmål for at starte vores chat

Sektion 3. Kapitel 2
some-alt