Skip to content

← Interfaces with Protocols and ABCs step 6 of 26

Medium Primitives

Inherit a Default Method from a Protocol

A class can fit a protocol in two ways. It can match the protocol's members, or it can inherit from the protocol like any other base class. This exercise shows what each way gives you.

The supplied Runner protocol lists run, and it also has a method with a body:

class Runner(Protocol):
    def run(self, payload: str) -> str: ...

    def run_twice(self, payload: str) -> str:
        return self.run(self.run(payload))

Only inheriting gives a class the protocol's code

  • A class that only matches Runner gets nothing from it. To fit, it needs its own run_twice.
  • A class that inherits from Runner gets run_twice through ordinary inheritance.
  • If a class such as Missing inherits from Runner but leaves out run, mypy treats run as abstract and rejects Missing(). Python still creates the object, because run has no @abstractmethod decorator. Calling its run returns None, whatever the annotation says.
  • Mark a protocol method with @abstractmethod, and Python also refuses to create the object, as it does for an ABC.

What to write

  • Implicit does not inherit from Runner. Its run returns payload.upper(). Add a run_twice that returns self.run(self.run(payload)). Without it, mypy rejects Implicit() as a Runner, because the run_twice member is missing.
  • Explicit(Runner) has a run that appends ".". Keep its @override, and do not define run_twice.
  • Leave Missing(Runner) empty, and leave AbcMissing(AbcRunner) as it is. AbcRunner.run has @abstractmethod.
  • try_build(cls: type) -> str calls cls(). Return "TypeError" if that raises TypeError, and "constructed" otherwise. Keep the parameter as plain type, so that mypy lets you pass a class it considers abstract.
  • solve(payload) creates runners: list[Runner] = [Implicit(), Explicit()] and returns this dictionary:
Key Value
"outputs" [runner.run_twice(payload) for runner 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)

What the report shows

For payload="ab", the outputs are ["AB", "ab.."]. The two is checks compare function objects. Explicit.run_twice is the protocol's own function, so the first check is True. Implicit.run_twice is a separate function, so the second is False.

Missing.__abstractmethods__ is empty, so try_build(Missing) returns "constructed". AbcMissing.__abstractmethods__ holds "run", so try_build(AbcMissing) returns "TypeError". mypy calls both classes abstract, but only the decorated method stops Python from creating one.