Type Hints Checkpoint
25 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Fix the mistakes
The slips that cost marks in this module. Spot each one, then check the fix.
A real module uses every tool from this module: containers, None, a protocol, and a checker. This checkpoint builds one small design and then tests your choices.
Give every part the type it needs
A small order system has three parts: a lookup that can fail, a rule that changes an amount, and a function that joins them. Ask of each one what goes in, what comes out, and whether it can be missing.
Example 1 · One typed design
from typing import Protocol
class Discount(Protocol): def apply(self, amount: float) -> float: ...
class Percent: def __init__(self, rate: float) -> None: self.rate = rate
def apply(self, amount: float) -> float: return amount - amount * self.rate / 100
def find_order(orders: dict[int, float], order_id: int) -> float | None: return orders.get(order_id)
def final_amount(orders: dict[int, float], order_id: int, discount: Discount) -> float | None: amount = find_order(orders, order_id) if amount is None: return None return discount.apply(amount)
orders = {101: 2000.0, 102: 450.0}print(final_amount(orders, 101, Percent(10)))print(final_amount(orders, 999, Percent(10)))1800.0 None
What this code does
Percentnever namesDiscount, but it hasapply, so it fits the protocol.- A missing order gives
None, whichfinal_amountpasses on to the caller.
The lookup returns float | None, because the order may not exist. The rule only needs an apply method, so it is a protocol, and any discount class fits. The function that joins them returns float | None as well, because the missing case passes through.
Let the checker guard None
Once a function returns float | None, every caller must deal with the None. mypy points at each caller that forgets. That is the main gain from the hints.
Example 2 · mypy finds the unhandled None
Runs in: Your terminal
mypy orders.pyorders.py:28: error: Unsupported operand types for + ("None" and "int") [operator]
orders.py:28: note: Left operand is of type "float | None"
Found 1 error in 1 file (checked 1 source file)What this code does
- The file adds
5to a result that might beNone. - The program would crash for a missing order. mypy reports it before that happens.
The fix is a test, such as if amount is not None. Do not silence the message with # type: ignore. That hides the exact case the hint was added to catch.
Questions to ask when hinting
Before you add a hint, ask these. Can this value be missing? Then use | None. Does the function accept anything with a certain method? Then use a protocol. Does the output type follow the input type? Then use a type variable.
Example 3 · After the None check
from typing import Protocol
class Discount(Protocol): def apply(self, amount: float) -> float: ...
class Percent: def __init__(self, rate: float) -> None: self.rate = rate
def apply(self, amount: float) -> float: return amount - amount * self.rate / 100
def find_order(orders: dict[int, float], order_id: int) -> float | None: return orders.get(order_id)
def final_amount(orders: dict[int, float], order_id: int, discount: Discount) -> float | None: amount = find_order(orders, order_id) if amount is None: return None return discount.apply(amount)
orders = {101: 2000.0, 102: 450.0}result = final_amount(orders, 101, Percent(10))total = (result if result is not None else 0.0) + 5print(total)1805.0
What this code does
- The conditional expression handles
Nonebefore adding. - mypy accepts this version and Python prints the same result.
Run mypy after each change. A clean run means the types agree, not that the logic is right, so keep your tests.
Finish up
Mark complete & continue
Next: Packages and __init__.py