TL;DR: Add type hints to all function parameters, return types, and non-obvious variables. Type hints enable static analysis, catch bugs before runtime, and make code self-documenting.
Python is dynamically typed, which means type errors often surface only at runtime -- or worse, in production. PEP 484 introduced type hints, allowing developers to declare expected types for variables and function signatures. Modern linters and IDEs interpret these annotations to warn about type mismatches during development, significantly reducing debugging time.
All function parameters and return types must have type hints. Variables should be annotated when the type is not obvious from the assignment.
Consider the following function without type hints:
def collatz(n):
if n % 2 == 0:
return n / 2
else:
return 3 * n + 1
print(collatz(5).denominator) # Works (int has .denominator)
print(collatz(6).denominator) # Fails (float has no .denominator)Without type annotations, this bug is invisible until execution. With type hints, the linter catches the mismatch immediately:
def collatz(n: int) -> int:
if n % 2 == 0:
return n / 2 # Linter warns: float is incompatible with int
else:
return 3 * n + 1def function(param: <type>) -> <return_type>:
...Example:
import pathlib
def delete_file(path: pathlib.Path) -> int:
"""Delete the file at the given path.
:param path: Path to the file to delete.
:return: 0 if deleted successfully, 1 otherwise.
"""
if path.is_file():
path.unlink()
return 0
return 1<variable>: <type> = <value>Example:
import queue
item: int = 1
some_queue: queue.Queue[int] = queue.Queue()
some_queue.put(item)The typing module supports advanced type constructs such as Union, Optional, Type, and generics:
from typing import Type, Union
def func(param: Type[Union[ClassA, ClassB]]) -> None:
...Alternative syntax:
from typing import Type, Union
def func(param: Union[Type[ClassA], Type[ClassB]]) -> None:
...For Python 3.10+, use the built-in | operator instead of Union:
def func(param: type[ClassA | ClassB]) -> None:
...# Wrong -- missing type hints on function signatures
def process(data):
return data.transform()
# Wrong -- using Any as a catch-all
from typing import Any
def process(data: Any) -> Any:
return data.transform()Define proper types or use Protocol for structural typing instead.