We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 6 of 24
Subclassing a Protocol: what Python will and will not stop you doing
“I inherited from the Protocol so Python will stop me forgetting a method.” It will not.
Protocol members are not @abstractmethod by default. A class that
explicitly subclasses a protocol and implements none of it is a mypy
[abstract] error at the point of instantiation — and constructs perfectly
happily at runtime. __abstractmethods__ on that subclass is empty. Ship
a plugin like that and it fails with an AttributeError deep inside a
request, not with a TypeError at construction.
Explicit subclassing is not useless, though: it is the only way to inherit a protocol’s default method bodies. An implicit implementer that wants the same behaviour has to copy it.
Your task
Given a protocol Runner with a required run(payload) -> str and a
default run_twice(payload) that returns self.run(self.run(payload)),
produce four classes:
-
Implicit— does not subclassRunner.runupper-cases. It must reimplementrun_twiceitself: the protocol declares that member, so a structural implementer needs it too, and gets no help writing it. -
Explicit(Runner)—runappends".", andrun_twiceis inherited. -
Missing(Runner)— declares nothing at all. -
AbcMissing(AbcRunner)— the ABC counterpart, for contrast.
def solve(payload: str) -> dict[str, object]:
builds runners: list[Runner] = [Implicit(), Explicit()] — proving the
structural and the nominal implementer are interchangeable at the type level
— and returns:
| key | value |
|---|---|
"outputs" |
[r.run_twice(payload) for r in runners] |
"explicit_inherits_default" |
Explicit.run_twice is Runner.run_twice |
"implicit_inherits_default" |
Implicit.run_twice is Runner.run_twice |
"protocol_subclass_abstracts" |
sorted(Missing.__abstractmethods__) |
"abc_subclass_abstracts" |
sorted(AbcMissing.__abstractmethods__) |
"protocol_subclass_build" |
try_build(Missing) |
"abc_subclass_build" |
try_build(AbcMissing) |
try_build(cls: type) -> str returns "TypeError" if cls() raises
TypeError, else "constructed".
Read the result carefully
Missing.__abstractmethods__ comes back empty and try_build(Missing)
returns "constructed". AbcMissing comes back with ["run"] and refuses
to build. Same omission, two completely different runtime guarantees — and
only one of them is the one people assume they are getting.
The identity checks are the other half: is comparison of the two function
objects shows, concretely, that the default body is shared with the
protocol in the explicit case and merely duplicated in the implicit one.
Practical rule
If you need construction-time enforcement, use an ABC. If you need to describe a shape you do not own, use a Protocol. Publishing both — the Protocol for callers, the ABC as an internal convenience base — is a legitimate and common design.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.