Understanding Python's Typing Module

Python is dynamically typed by default. A variable like x = 4
can later become x = "hello"
with no complaints from the interpreter. However, as Python projects grow, it becomes crucial to improve code readability, debuggability, and collaboration. This is where the typing
module (introduced in Python 3.5 via PEP 484) becomes valuable.
Why Use Type Hints?
Type hints provide a way to explicitly declare the expected data types of variables and function arguments/returns.
Improved Readability: Explicitly documents variable and function types.
Early Error Detection: Type checkers (like
mypy
) can catch bugs before runtime.Better Developer Onboarding: New developers can understand the codebase more quickly.
Enhanced IDE Support: Features like autocomplete, linting, and refactoring are improved.
Type Annotations for Variables
You can annotate variables with specific types to indicate what kind of value is expected.
X: int = 5
X: int = "hello" # No runtime error, but static checker like mypy will warn
To check types:
pip install mypy
mypy your_file.py
Function Annotations
Function annotations specify the expected input and return types of functions.
def add(a: int, b: int, c: int) -> int:
return a + b + c # Takes three integers and returns an integer
def do_nothing() -> None:
pass # Explicitly returns nothing
List Type
Use List[T]
to annotate lists that contain elements all of the same type T
, with a dynamic and mutable size.
from typing import List
matrix: List[List[int]] = [[1, 2], [3, 4]] # 2D list of integers
Tuple Type
Use Tuple[X, Y, Z]
to annotate fixed-length tuples where each element has a specific, predefined type.
from typing import Tuple
user_info: Tuple[int, int, str] = (42, 27, "admin") # Fixed-length tuple with known types
Use Tuple[T, ...]
to annotate variable-length tuples where all elements share the same type T
.
from typing import Tuple
scores: Tuple[int, ...] = (90, 85, 100, 78)
# Tuple of any length where every element is an int
Dict Type
Use Dict[K, V]
for dictionaries with specific key and value types.
from typing import Dict
roles: Dict[str, str] = {"alice": "admin"} # Dict with str keys and str values
Set Type
Use Set[T]
to define sets containing only elements of type T
.
from typing import Set
numbers: Set[int] = {1, 2, 3} # Set of integers
letters: Set[str] = {"a", "b"} # Set of strings
Optional Type
Optional[T]
means the value can either be of type T
or None
.
from typing import Optional
def get_name(id: str) -> Optional[str]:
... # Can return a string or None
from typing import Optional
def foo(output: Optional[bool] = False) -> None:
# The 'output' parameter can be either a bool or None.
# Defaults to False if not provided.
Any Type
Any
disables type checking for a variable or parameter. It can be any type.
from typing import Any
def log(input: Any) -> Any:
return input # Accepts and returns any type
Sequence Type
Use Sequence[T]
to represent ordered collections like lists, tuples, or strings.
from typing import Sequence
def print_all(items: Sequence[str]):
for item in items:
print(item)
# All valid
print_all(["a", "b", "c"]) # List
print_all(("x", "y", "z")) # Tuple
print_all("hello") # String is also a sequence
# Invalid
print_all({"a", "b", "c"}) # set is unordered and not considered a sequence, mypy will raise an error
Callable Type
Use Callable[[ArgTypes], ReturnType]
to annotate function parameters that accept other functions.
from typing import Callable
def operate(f: Callable[[int, int], int]) -> None:
print(f(2, 3)) # Accepts a function taking two ints and returning an int
def add(x: int, y: int) -> int:
return x + y
operate(add)
Type Aliases or Custom Type
Create readable aliases for complex or reused type structures.
from typing import List, Tuple, Union, Dict
# Simplifying a complex list of tuples
Coordinates = List[Tuple[int, int]] # Here Coordinates is the alias name
points: Coordinates = [(1, 2), (3, 4)]
# Using Union for a variable that can be an int or a string
IntOrString = Union[int, str]
data: IntOrString = 42 # Could also be a string
# For a dictionary with string keys and values that are either integers or floats
ValueDict = Dict[str, Union[int, float]]
my_dict: ValueDict = {"age": 30, "temperature": 98.6}
Union Type
A Union
means a variable can be one of several specified types.
Basic Syntax (Pre–Python 3.10)
from typing import Union
# This variable can be either an int or a str
number_or_string: Union[int, str] = 5
Use Case 1: Function Parameters
Allows accepting multiple input types with appropriate handling.
from typing import Union
def process_input(value: Union[int, str]) -> Union[float, str]:
if isinstance(value, str):
return f"Received string: {value}" # Returns string if input is a string
else:
return float(value) # Converts int to float and returns
Use Case 2: Return Values
Used when a function might return multiple types depending on logic.
from typing import Union
def get_data(key: str) -> Union[dict, list, str]:
"""
Returns different data types based on the provided key.
Could return a dictionary, list, or string.
"""
...
Use Case 3: Variables with Multiple Possible Types
Used when the type of a variable may change over time.
from typing import Union
result: Union[int, None] = None # Initially None
result = 10 # Later assigned an int
Python 3.10+ Syntax Using | (PEP 604)
Python 3.10 introduced cleaner syntax using the |
(pipe) symbol.
# Variable can be an int or a str
number_or_string: int | str = 5
# Function example with the new syntax
def process_input(value: int | str) -> float | str:
if isinstance(value, str):
return f"Received string: {value}"
else:
return float(value)
Generics with TypeVar
Use TypeVar
to define generic functions or classes that work with multiple types.
from typing import TypeVar, List
T = TypeVar('T') # Define a generic type variable T
def get_element(lst: List[T], index: int) -> T:
"""
Returns the element at the given index from a list.
The list can be of any type, but all elements must be of the same type T.
"""
return lst[index]
# Examples of usage:
print(get_element([10, 20, 30], 1)) # Output: 20 (int)
print(get_element(["apple", "banana"], 0)) # Output: "apple" (str)
print(get_element([[1, 2], [3, 4]], 1)) # Output: [3, 4] (List[int])
Summary:
Type hints help make Python code easier to read, debug, and maintain. They allow you to specify expected types for variables and functions, which improves code clarity and catches potential errors early. As projects grow, using type hints becomes essential for better collaboration and tool support.
Subscribe to my newsletter
Read articles from Suraj Rao directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Suraj Rao
Suraj Rao
Hi, I'm Suraj I'm an aspiring AI/ML engineer passionate about building intelligent systems that learn, see, and adapt to the real world.