Skip to Content
Course content

301: Type Checking with mypy

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're preparing for a long-haul flight. You have a pre-flight checklist: fuel levels checked, flaps tested, engines humming. You don't wait until you're 30,000 feet in the air to realize you forgot to lock the cargo bay; you want that mistake caught while the plane is still sitting safely on the tarmac. If you find the error during the checklist, it's a five-minute fix. If you find it mid-flight, it's a catastrophe.

In Python, running your code is "taking off." Type hints are your checklist. But here is the catch: Python itself doesn't actually care about those hints. It ignores them completely at runtime. This is where mypy comes in. Mypy is your ground crew. It reads your checklist and your code, then tells you exactly where you're about to crash before you ever hit the "Run" button.

Catching the "Invisible" Crash

I've spent far too many late nights debugging TypeError: unsupported operand type(s) for -: 'float' and 'str'. Usually, it happens because some function three levels deep in the call stack decided to return a string instead of a number, and I didn't notice until the app crashed in production.

Look at this snippet. It looks fine, and it works if you pass it the right data:

def apply_discount(price: float, discount: float) -> float:
    return price - discount

# This works great
print(apply_discount(100.0, 15.0))

# This will crash at runtime, but Python won't warn you while writing it
print(apply_discount(100.0, "15.0")) 

If you run this with the standard Python interpreter, it just crashes. But if you run mypy over this file, it screams at you immediately: Argument 2 to "apply_discount" has incompatible type "str"; expected "float". It found the bug without ever executing a single line of code.

Bridging the Gap Between Hints and Reality

The magic of mypy is that it turns Python's dynamic nature into something more predictable. You aren't changing how the code runs—you're just adding a layer of verification. To get this working, you'll need to install it via pip (pip install mypy) and then run it from your terminal like this:

mypy my_script.py

I highly recommend doing this as part of your CI/CD pipeline or as a pre-commit hook. I don't trust my own brain to remember every type across a 1,000-line file, and I certainly don't trust my teammates to always pass the correct types into my functions. Mypy becomes the "single source of truth" that keeps the codebase honest.

Handling the "Maybe" Types

One thing that usually trips people up is when a value might be a certain type, or it might be None. In the old days, we just hoped for the best. Now, we use Optional from the typing module.

If you tell mypy a variable is a float, but then you try to assign None to it, mypy will flag it. This forces you to actually handle the None case with an if statement, which prevents the dreaded AttributeError: 'NoneType' object has no attribute...

from typing import Optional

def get_user_score(user_id: int) -> Optional[int]:
    # Imagine this looks up a score in a database
    if user_id == 1:
        return 100
    return None

score = get_user_score(2)
# mypy will warn you here: "Item 'None' of 'Optional[int]' has no attribute 'bit_length'"
# print(score.bit_length()) 

if score is not None:
    print(score.bit_length()) # Now mypy is happy



📋 Practical Task

Fixing the Inventory Management System

You've been handed a script for a warehouse inventory system. The previous developer added some type hints, but they were sloppy, and the code is riddled with potential runtime crashes. Your goal is to use mypy to identify the type mismatches and fix the code so that it passes a static type check.

The Buggy Code:

from typing import List, Optional

def calculate_total_value(items: List[float], tax_rate: float) -> float:
    total = sum(items)
    return total * (1 + tax_rate)

def get_item_price(item_id: int) -> Optional[float]:
    prices = {1: 10.50, 2: 20.00, 3: 5.25}
    return prices.get(item_id)

# --- THE PROBLEM AREA ---
inventory_ids = [1, 2, 4] # Item 4 doesn't exist in the prices dict
prices = []

for i in inventory_ids:
    price = get_item_price(i)
    prices.append(price) # Mypy should flag this: you're adding Optional[float] to List[float]

# This will crash if any item was None
final_value = calculate_total_value(prices, 0.07)
print(f"Total Value: {final_value}")

Your Task:

  • Install mypy and run it against this script.
  • Identify the two primary type errors mypy reports.
  • Modify the loop and the prices list definition so that only actual floats are passed to calculate_total_value.
  • Ensure the final script runs without any mypy errors.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.