We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 5 of 24
@runtime_checkable checks names, not types
Teams reach for @runtime_checkable as a plugin-validation gate — “if it
passes isinstance, it implements the interface” — and get something much
weaker than they think. isinstance against a protocol verifies that the
attribute names exist. Nothing else.
What is actually verified
All of the following are true, and all of them are checkable in this problem:
-
A class whose
close()takes no arguments passes against a protocol demandingclose(timeout: float). Signatures are not compared. -
An attribute holding
Nonesatisfies a member declaredname: str. Value types are not compared. -
issubclass()against a protocol with any non-method member raisesTypeError: Protocols with non-method members don't support issubclass(). (mypy will also refuse to compile the call — error codemisc.) -
isinstanceagainst an undecorated protocol raisesTypeErrortoo. -
Since 3.12 the lookup uses
inspect.getattr_static, so a__getattr__that fabricates attributes no longer fools it, and the member list is frozen at protocol-class creation. -
The docs recommend plain
hasattron hot paths — the protocol check is considerably slower and buys you nothing extra.
Your task
You are given five candidate classes, a data protocol Backend
(name: str plus close(timeout: float)), and a method-only protocol
Closable. Implement:
def members_valid(obj: object) -> bool
def select_backends(candidates: Iterable[object]) -> list[str]
def closable_subclasses(candidates: Iterable[object]) -> list[str]
def solve(candidates: list[str]) -> dict[str, object]
members_valid is the check isinstance does not do: name must actually
hold a str, close must be callable, and its signature must have a
timeout parameter. Use inspect.signature, and guard it — signature()
raises TypeError/ValueError on some callables.
select_backends keeps a candidate only if it passes isinstance and
members_valid; closable_subclasses reports which candidate types pass
issubclass(type(c), Closable). Both return type(c).__name__, preserving
input order.
solve builds each candidate named in candidates from REGISTRY and
returns:
{"isinstance_accepts": ..., "validated_accepts": ..., "closable_subclasses": ...}
The gap between the first two lists is the entire lesson: NullName and
NoTimeout are decoys with all the right attribute names and the wrong
contents, and the naive check waves them straight through.
Where the type system does the work
members_valid must take object, not Backend. If you narrow first and
then re-check the narrowed value, mypy knows obj.name is already str,
the failure branch becomes unreachable, and --warn-unreachable fires. The
validation has to run on the un-narrowed value — which is also the honest
description of what it is doing.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.