Skip to content

← From Everyday Python to Production Code step 4 of 223

Easy Primitives

Placement 2/6: Match Objects by the Methods They Have

Solve this without the hints and you can skim the protocols track.

Duck and Robot each have a speak() -> str method. Rock has roll() instead. They share no base class. You want to ask any of them to speak, if it can.

A protocol describes that need. It is a class that lists the methods an object must have. Any class with those methods matches it, without inheriting from it. The starter defines one:

class Speaker(Protocol):
    def speak(self) -> str: ...

To mypy, an abstract base class, or ABC, matches only its subclasses. Using one here would mean changing Duck and Robot to inherit from it. A protocol leaves them alone.

What to write

Add the @runtime_checkable decorator to Speaker. Without it, isinstance(obj, Speaker) raises TypeError. Then write solve(order: list[str]) -> list[str].

Leave Duck, Robot, Rock, REGISTRY and the speak signature as they are. For each name in order, first to last, add one string to the result list:

  1. Look up its factory in REGISTRY. If the name is not there, add "<unknown>" and go on to the next name.
  2. Call the factory to build a new object.
  3. If isinstance(obj, Speaker) is true, add the result of obj.speak(). Otherwise add "<silent>".

The annotation on REGISTRY says every factory returns object, so mypy cannot tell which class you get back. The isinstance check tells mypy the object is a Speaker, so obj.speak() type-checks and returns a str.

Example

solve(["robot", "rock", "Duck", "duck"])
# ["beep", "<silent>", "<unknown>", "quack"]

"Duck" is unknown because the registry key is lowercase.

Edge cases

  • Keys are case-sensitive.
  • Repeated names give repeated results. Return one result per input name.
  • An empty list returns [].

At runtime, isinstance checks only that the object has an attribute called speak. It does not check the arguments or the return type, so an object with speak = 42 would pass. Only mypy, reading the annotations, checks the signature.