Type Hints
Python never makes you say what type a variable holds. That keeps code short, but in bigger programs it can be hard to tell what a function expects: is price a number or a string? Is items a list or a dictionary? Type hints let you write that information into the code itself.
Your first type hints
Put a colon and a type after a parameter name, and an arrow -> with a type before the colon that ends the def line:
def greet(name: str, times: int) -> str:
return ("Hello " + name + "! ") * times
print(greet("Ada", 2))Read it as: greet takes a name that is a str and times that is an int, and it returns a str.
The code runs exactly the same as without the hints. They're there for people reading the code, and for tools such as code editors, which use them to catch mistakes and suggest completions as you type.
Python doesn't enforce them
This is the most important thing to know about type hints: Python itself ignores them when running your code. Passing the "wrong" type doesn't cause an error on its own:
def double(n: int) -> int:
return n * 2
print(double(5))
print(double("ha")) # breaks the hint, but Python runs it anywayTo actually check hints, programmers run a separate tool called a type checker, such as mypy, before running the program. It reads the hints and reports mistakes like double("ha") without running anything. Code editors such as VS Code do this checking as you type.
Hints for variables
You can hint variables too, although it's mostly useful when the value doesn't make the type obvious:
count: int = 0 name: str = "Ada" price: float = 9.99 is_ready: bool = False print(count, name, price, is_ready)
Hints for collections
Put the type of the items in square brackets:
| Hint | Means |
|---|---|
list[int] |
a list of ints |
set[str] |
a set of strings |
dict[str, float] |
a dictionary with string keys and float values |
tuple[int, int] |
a tuple of exactly two ints |
tuple[str, ...] |
a tuple of any number of strings |
def average(scores: list[float]) -> float:
return sum(scores) / len(scores)
def stock_value(stock: dict[str, int], prices: dict[str, float]) -> float:
return sum(count * prices[item] for item, count in stock.items())
def min_max(numbers: list[int]) -> tuple[int, int]:
return min(numbers), max(numbers)
print(average([7.5, 9.0, 6.0]))
print(stock_value({"apple": 10, "pear": 4}, {"apple": 0.5, "pear": 0.8}))
print(min_max([4, 9, 1]))Values that might be missing
Often a function returns a value or None, such as a search that might not find anything. Write that as int | None, read as "an int or None":
def find_age(people: dict[str, int], name: str) -> int | None:
return people.get(name)
ages = {"Ada": 36, "Grace": 85}
print(find_age(ages, "Ada"))
print(find_age(ages, "Alan"))The | means "or", and it works for any types: int | float, str | list[str], and so on.
A function that doesn't return anything is hinted -> None:
def log(message: str) -> None:
print("[log]", message)
log("Started")Seeing the hints
Python stores a function's hints in a dictionary called __annotations__. Printing it shows exactly what was written:
def greet(name: str, times: int) -> str:
return ("Hello " + name + "! ") * times
print(greet.__annotations__)The exercises below use this to check your hints.
Hints with classes
Your own classes work as types too. Hints are also how dataclasses (from the Special Methods lesson) know which fields to create:
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
in_stock: bool = True
def cheapest(products: list[Product]) -> Product:
return min(products, key=lambda p: p.price)
items = [Product("Pen", 1.5), Product("Book", 12.99), Product("Eraser", 0.75)]
print(cheapest(items))Going deeper: Type aliases and the typing module optional
Long hints can be given a name, called a type alias, to keep code readable:
Scores = dict[str, list[int]]
def best_student(scores: Scores) -> str:
return max(scores, key=lambda name: sum(scores[name]))
print(best_student({"Mia": [80, 90], "Leo": [95, 99]}))You'll also see the typing module in older code. Before Python 3.9 and 3.10 added the shorter forms, people wrote List[int], Dict[str, int] and Optional[int]. They mean the same as list[int], dict[str, int] and int | None:
from typing import Optional, List
def first(items: List[str]) -> Optional[str]:
return items[0] if items else None
print(first(["a", "b"]))
print(first([]))typing also has Any, for "any type at all", and Callable, for functions.
When to use type hints
- Function parameters and return values are where hints help most, because they're the "contract" other code relies on.
- Small scripts and quick experiments often don't need them.
- Be honest: a hint that doesn't match what the code really does is worse than no hint.
Type hints are optional, so you can add them gradually, starting with the functions you use most.
Exercises
Exercise 1: Add the hints
Add type hints to describe: name is a str, age is an int, and it returns a str. The last line prints the hints so the auto-check can see them.
def describe(name, age):
return f"{name} is {age} years old"
print(describe("Ada", 36))
print(describe.__annotations__)Put : str after name and : int after age inside the brackets.
The return type goes after ->, before the colon at the end of the line: ) -> str:.
Exercise 2: A list of numbers
Add hints so that total takes a list of floats and returns a float.
def total(prices):
return sum(prices)
print(total([2.5, 4.0, 1.25]))
print(total.__annotations__)A list of floats is written list[float].
The full line is def total(prices: list[float]) -> float:.
Exercise 3: Maybe a result
find_price returns a price from the dictionary, or None if the item isn't there. Add hints: prices is a dictionary of str keys and float values, item is a str, and the return type is "float or None".
def find_price(prices, item):
return prices.get(item)
menu = {"tea": 2.5, "cake": 3.75}
print(find_price(menu, "cake"))
print(find_price(menu, "soup"))
print(find_price.__annotations__)A dictionary with str keys and float values is written dict[str, float].
"float or None" is written float | None.
Exercise 4: Fix the wrong hint
The return hint on this function is wrong: it says int, but the function returns text. Fix it.
def full_name(first: str, last: str) -> int:
return first + " " + last
print(full_name("Ada", "Lovelace"))
print(full_name.__annotations__["return"])Look at what the function returns: two strings joined with +. What type is that?
Change -> int to -> str.
Type hints at a glance
A quick reference for later.
| Hint | Means | Example |
|---|---|---|
int, float, str, bool |
A single value of that type | age: int = 36 |
list[int] |
A list of ints | scores: list[int] = [90, 85] |
dict[str, float] |
A dictionary with string keys and float values | prices: dict[str, float] = {"tea": 2.5} |
set[str] |
A set of strings | tags: set[str] = {"python"} |
tuple[int, int] |
A tuple of exactly two ints | point: tuple[int, int] = (3, 4) |
tuple[str, ...] |
A tuple of any number of strings | names: tuple[str, ...] = ("Ada", "Alan") |
int | None |
An int, or None |
def find(name: str) -> int | None: |
int | str |
An int or a string | def show(value: int | str) -> None: |
-> None |
The function returns nothing | def log(msg: str) -> None: |
Point (a class name) |
An object of that class | def move(p: Point) -> Point: |
Optional[int] |
Older spelling of int | None (from typing import Optional) |
def find(name: str) -> Optional[int]: |
Any |
Any type at all; switches checking off (from typing import Any) |
def debug(value: Any) -> None: |
Callable[[int], str] |
A function that takes an int and returns a string (from collections.abc import Callable) |
def apply(f: Callable[[int], str]) -> str: |
Summary
- Type hints describe what types a function expects and returns:
def f(name: str) -> int:. - Python ignores hints when running code. Tools like
mypyand code editors check them. - Collections take item types in brackets:
list[int],dict[str, float],tuple[int, int]. X | Nonemeans "an X, or None".-> Nonemeans the function returns nothing.- A function's hints are stored in its
__annotations__dictionary. - Hints are optional, so add them where they make code clearer, starting with function signatures.