Type Hints
In Python 3.5, type hints were introduced as a means for adding type information. Starting with Python 3.9, many of these types are available as built-ins, so you no longer need to import them from the typing
module. This page assumes you are using Python 3.9+.
Think of type hints primarily as a documentation tool or a helper for IDEs and static analysis. The Python interpreter does not enforce type hints at runtime, but they provide valuable information for you as a developer and for various tools.
The primary benefit of using type annotations is to document your code and catch potential bugs early. They are particularly helpful with complex data structures, for example, when making a list of tuples.
my_list_of_tuples: list[tuple[str, int]] = []
Basic Types
The core basic types: str
, int
, float
, and bool
.
my_string: str = ""
my_int: int = 0
my_float: float = 0.0
my_bool: bool = True
The core collection types: list
, dict
, tuple
, and set
.
my_list: list[str] = []
my_dict: dict[str, int] = {}
my_tuple: tuple[str, int] = ("hello", 42) # Fixed-length tuple
my_set: set[int] = set()
For variable-length tuples, use ellipsis:
my_var_tuple: tuple[int, ...] = (1, 2, 3, 4, 5) # Variable-length tuple
Functions
To use type hints in a function for arguments and return values.
def add(a: int, b: int) -> int:
return a + b
Use None
when your function does not return a value.
def hello(name: str) -> None:
print(f"Hello {name}")
Functions with default arguments:
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
Classes
To use type hints in a class.
class Point:
x: int
y: int
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def distance_from_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
Custom Types
You can use your own classes as type hints in other places:
def move_point(point: Point, dx: int, dy: int) -> Point:
return Point(point.x + dx, point.y + dy)
my_point: Point = Point(1, 2)
new_point: Point = move_point(my_point, 3, 4)
Optional Types
For variables that can be None
, use the union syntax (|
) with None
.
# Modern syntax (Python 3.10+)
my_optional_string: str | None = None
For older Python versions, you can still import Optional
:
from typing import Optional
my_optional_string: Optional[str] = None
Union Types
Use the |
operator for variables that can hold values of multiple types (Python 3.10+):
# Modern syntax
my_union: int | str = "Hello"
my_union = 123
# Multiple types
id_value: int | str | None = None
For older Python versions:
from typing import Union
my_union: Union[int, str] = "Hello"
Literal Types
Use Literal
for values that must be specific literals:
from typing import Literal
def set_mode(mode: Literal["read", "write", "append"]) -> None:
print(f"Setting mode to {mode}")
# This will work
set_mode("read")
# This will cause a type checker error
set_mode("invalid")
Any Type
Use Any
when you need to opt out of type checking. Try to limit its use, as it bypasses type checking.
from typing import Any
def process_data(data: Any) -> Any:
# This function can accept and return anything
return data
Type Aliases
Create type aliases for complex types:
# Modern syntax using built-in types
Coordinates = list[tuple[float, float]]
UserID = int
JSONData = dict[str, Any]
my_coordinates: Coordinates = [(1.0, 2.0), (3.0, 4.0)]
user_id: UserID = 12345
Generics and TypeVar
Use generics and TypeVar
to create generic functions and classes:
from typing import TypeVar, Generic
T = TypeVar('T')
def first_item(items: list[T]) -> T | None:
return items[0] if items else None
# Usage
numbers = [1, 2, 3]
first_num = first_item(numbers) # Type checker knows this is int | None
strings = ["a", "b", "c"]
first_str = first_item(strings) # Type checker knows this is str | None
Generic classes:
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T | None:
return self._items.pop() if self._items else None
# Usage
int_stack: Stack[int] = Stack()
str_stack: Stack[str] = Stack()
Final Types
Use Final
for constants that should not be reassigned:
from typing import Final
MAX_CONNECTIONS: Final = 100
API_VERSION: Final[str] = "v1.2.3"
# This would cause a type checker error:
MAX_CONNECTIONS = 200
When to Use Type Hints
Type hints are most beneficial when you are:
- Working with complex data structures
- Building libraries or APIs that others will use
- Working in teams where code clarity is important
- Wanting better IDE support and error detection
You might skip type hints for:
- Simple scripts or one-off utilities
- Prototyping or exploratory code
- Situations where the types are obvious from context
Tools
Most editors these days have support for type hints to provide better autocomplete and error checking. Popular type checkers include:
mypy
The original and most widely used type checker:
# Install mypy
pip install mypy
# Run mypy on a file
mypy my_file.py
# Run mypy on a directory
mypy src/
Pyright/Pylance
Microsoft’s type checker, used in VS Code’s Pylance extension:
# Install pyright
npm install -g pyright
# Run pyright
pyright src/
Configuration
You can configure type checkers with configuration files:
mypy.ini or pyproject.toml for mypy:
[mypy]
python_version = 3.9
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
pyrightconfig.json for Pyright:
{
"pythonVersion": "3.9",
"typeCheckingMode": "strict",
"reportMissingImports": true
}