Skip to content

Week 2 · Python for real work

Type hints

Label your variables, parameters and return values with the kind of data they hold, so your code is easier to read and tools can catch mistakes.

Live session

A type hint is a label that says what kind of data a variable, parameter or return value should hold. A function works fine without hints, but with them you can understand it at a glance:

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

From the first line alone, you know short_code takes a string and returns a string. You don't have to read the rest of the function.

Hints help the next person who reads your code, and they help tools such as VS Code and mypy find your mistakes. Python itself doesn't check them when your program runs.

How to follow along

Create a separate project for this lesson and the next three. Your learn-python project has practice files with mistakes in them on purpose, and you don't want mypy reporting those:

cd ~/projects/fde-course/week-02
uv init --no-package --python ">=3.14" learn-types
cd learn-types
code .

Create a file called hints.py, type each example into it and run it with:

uv run hints.py

Don't call the file types.py, because Python has a standard library module with that name.

Hints on functions

Write parameter: type for each parameter, and put the return type after ->:

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


def greet(name: str) -> None:
    print(f"Hello, {name}")


def add_tax(total: float, rate: float = 0.18) -> float:
    return total + total * rate


print(add_delivery(1000, 350))
greet("Noor Foods")
print(add_tax(1000))

Output:

1350
Hello, Noor Foods
1180.0
  • -> None means the function doesn't return anything useful. It's there to do a job, such as printing.
  • A parameter with a default value has its type first and its default after it: rate: float = 0.18.

Hints on variables

You can label variables too, but you rarely need to. Here, the value already makes the type obvious:

customer = "Noor Foods"

An empty collection is different, because nothing shows what's meant to go in it. A hint makes that clear:

stock_counts: dict[str, int] = {}

Hints for collections

Square brackets say what a collection holds:

hints.py
def split_tags(raw: str) -> list[str]:
    tags = []
    for tag in raw.split(","):
        tags.append(tag.strip())
    return tags


print(split_tags("rice, oil ,sugar"))

Output:

['rice', 'oil', 'sugar']
HintWhat it means
list[str]A list of strings
dict[str, int]A dictionary with string keys and whole number values
tuple[float, float]A tuple of exactly two decimal numbers
set[str]A set of strings

Types that take a type in square brackets like this are called generic types.

A value that might be missing

A lookup might not find anything, so a value can be a string or None. Use |, read as "or", to allow more than one type:

hints.py
def find_phone(phones: dict[str, str], customer: str) -> str | None:
    return phones.get(customer)


phones = {"Noor Foods": "0300-1234567"}
print(find_phone(phones, "Noor Foods"))
print(find_phone(phones, "Bilal Traders"))

Output:

0300-1234567
None

You'll often see | None with a default value, for an optional parameter:

def archive_customer(name: str, reason: str | None = None) -> None:
    ...

Your own classes and enums work as types too, such as def archive_all(customers: list[Customer]) -> None:.

Hints don't check anything by themselves

Python ignores type hints when it runs your code. This breaks the hint, but it runs anyway:

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


print(add_delivery("noor", "foods"))

Output:

noorfoods

To catch this before the code runs, you need a type checker, which is the next lesson. To check data while the program runs, such as data from a form or an API, you'll use Pydantic.