Skip to content

Week 2 · Python for real work

Checking types with mypy

Use mypy to find mistakes in your code before you run it, by checking that your values match your type hints.

Live session

Python ignores type hints when it runs your code, so they need a tool to make them useful. A type checker reads your hints and tells you where the code doesn't match them. The standard one is mypy.

mypy reads your code without running it. That's why it can find a mistake in a part of the program that hardly ever runs, before a customer reaches it.

Install and run mypy

Only developers need mypy, not the finished program, so install it as a development dependency with --dev. Work in your learn-types project:

uv add --dev mypy

To check every Python file in the project, run:

uv run mypy .

The . means "this folder". If everything matches, you'll see a line like this, with your own number of files:

Success: no issues found in 2 source files

To check one file, use its name: uv run mypy hints.py.

The Mypy extension you installed in VS Code shows the same kind of problems as underlines while you type.

Catching mistakes

A wrong return type

This function says it returns a whole number, but it returns a string. Put it in hints.py:

hints.py
def short_code(name: str) -> int:
    return name.strip().lower().replace(" ", "-")

Run uv run mypy hints.py:

hints.py:2: error: Incompatible return value type (got "str", expected "int")  [return-value]
Found 1 error in 1 file (checked 1 source file)

Read it from left to right: the file, the line number, then what's wrong. The name in square brackets is the kind of error.

A wrong argument

hints.py
def add_delivery(total: int) -> int:
    return total + 350


print(add_delivery("1000"))
hints.py:5: error: Argument 1 to "add_delivery" has incompatible type "str"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

A value that might be None

This is where mypy helps most. to_int() can return None, and this code forgets that:

hints.py
def to_int(text: str) -> int | None:
    try:
        return int(text)
    except ValueError:
        return None


quantity = to_int("5")
print(quantity + 1)
hints.py:9: error: Unsupported operand types for + ("None" and "int")  [operator]
hints.py:9: note: Left operand is of type "int | None"
Found 1 error in 1 file (checked 1 source file)

This code works when you run it, because "5" is a valid number. It would only crash on bad input, which might not happen until much later. To fix it, check for None first:

quantity = to_int("5")
if quantity is not None:
    print(quantity + 1)

mypy understands that check, so the error goes away.

Stricter checking

By default, mypy skips any function that has no type hints at all, so it can't find mistakes in them. To require hints everywhere, add this to the end of your project's pyproject.toml:

pyproject.toml
[tool.mypy]
strict = true

Now a function without hints is reported as an error:

hints.py
def add_delivery(total):
    return total + 350
hints.py:1: error: Function is missing a type annotation  [no-untyped-def]
Found 1 error in 1 file (checked 1 source file)

Use strict on new projects, because it's easiest to add hints as you write the code.