We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Interfaces with Protocols and ABCs step 5 of 26
Catch the Backends That isinstance Lets Through
The type hints track showed that isinstance against a @runtime_checkable
protocol checks only that each member exists, not its type or signature. This
exercise adds a data member and a class check, and you write the validation
that isinstance skips.
The starter's Backend protocol has a data member and a method:
@runtime_checkable
class Backend(Protocol):
name: str
def close(self, timeout: float) -> None: ...
isinstance(obj, Backend) is True for an object whose name is None,
and for one whose close takes no arguments. Code that relies on that check
crashes on both.
What to write
members_valid(obj: object) -> bool makes these checks in order:
- Read
namewithgetattr(obj, "name", None). ReturnFalseunless it is astr. - Read
closewithgetattr(obj, "close", None). ReturnFalseunless it is callable. - Call
inspect.signature(close). ReturnFalseif that raisesTypeErrororValueError. - Return whether the signature has a parameter whose name is exactly
"timeout".
Never call close. Only the parameter's name matters, not its type, default
or kind.
select_backends(candidates) keeps each candidate that passes
isinstance(c, Backend) and then members_valid(c), checked in that order.
It returns type(c).__name__ for each candidate it keeps.
closable_subclasses(candidates) returns type(c).__name__ for each
candidate where issubclass(type(c), Closable) is True. It does not call
members_valid. It uses Closable, which is Backend without name,
because issubclass refuses a protocol that has a data member:
TypeError: Protocols with non-method members don't support issubclass(). Non-method members: 'name'.
Both functions keep the input order and any duplicates. Read the input only once, because it may be a generator.
solve(candidates) builds one fresh object for each key with
REGISTRY[key](), and returns a dictionary with three lists:
"isinstance_accepts", from the suppliednaive_select."validated_accepts", fromselect_backends."closable_subclasses", fromclosable_subclasses.
An empty input gives three empty lists.
The supplied candidates
| Candidate | isinstance with Backend |
members_valid |
issubclass with Closable |
|---|---|---|---|
Postgres, Sqlite |
Passes | Passes | Passes |
NullName |
Passes | Fails, name is None |
Passes |
NoTimeout |
Passes | Fails, no timeout parameter |
Passes |
NoClose |
Fails | Not reached | Fails |
For example, solve(["null_name", "sqlite", "sqlite"]) returns
["NullName", "Sqlite", "Sqlite"] for "isinstance_accepts" and for
"closable_subclasses", and ["Sqlite", "Sqlite"] for "validated_accepts".
The timeout check is deliberately small. A close that also requires a
second argument passes it.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.