Is there a downside to `typing.runtime_checkable`?

Is there a downside to adding the runtime_checkable decorator to a Protocol definition? I can’t find one, and I’m curious why the capability wasn’t just added to the Protocol type by default!

from typing import Protocol, runtime_checkable

@runtime_checkable
class Runnable(Protocol):
  """This is nothing new!"""
  def __run__(self) -> None:
    """Run something."""

class RoadRunner:
  """A concrete implementation."""
  def __run__(self) -> None:
    """Run!"""

bird = RoadRunner()

assert isinstance(bird, Runnable)

The downside is implying a more rigorous check than what it actually does. The check simply ensures that the relevant methods exist on the class, it doesn’t check for argument count, names, type hints etc. If Protocol defaulted to enabling runtime checks, it might be used by users of the library and then unexpectedly get false positives.

2 Likes

Using isinstance checks provided by runtime_checkable can also be surprisingly slow.

You may also be interested in the relevant “rejected ideas” section of the PEP: PEP 544 – Protocols: Structural subtyping (static duck typing) | peps.python.org

2 Likes

Hm, okay. The purpose of the rest of my reply here is to understand, I am not arguing against past decisions. I am trying to understand the mechanisms behind Python typing.

Looking at the rejected ideas section…

The problem with this is instance checks could be unreliable, except for situations where there is a common signature convention such as Iterable. For example:

I don’t understand that logic. Is there some reason why isinstance checks have to be unreliable? Am I right in understanding that the runtime_checkable implementation is incomplete — it “only” validates the names of the methods, and not the full signatures — and for that reason, Protocol types are not runtime_checkable by default? That feels a bit odd to me. Is there a technical reason I’m missing for why runtime_checkable doesn’t validate the full signature?

In general the static type system carries information around that isn’t represented at runtime; things like variance, newtype, generics, etc — so this is actually impossible. For the narrower question of signature validation, in theory you could make something like runtime_checkable that uses inspect.signature, but it would be much slower, might not handle things like **kwargs the way people expect, would have been unsound without positional-only params (which people still get surprised by), would have been a breaking change in collections.abc, etc

2 Likes

Awesome write up, I understand this a lot better. Thank you!