typing Module Complexity¶
The typing module provides support for type hints, allowing developers to annotate function parameters and return types for better code documentation and static analysis.
Complexity Reference¶
| Operation | Time | Space | Notes |
|---|---|---|---|
| Type annotation | O(1) | O(1) | Define hint (no runtime check) |
get_type_hints() |
O(k) | O(k) | k = number of annotations; involves string evaluation |
Type checking with isinstance() |
O(1) | O(1) | Runtime check (limited) |
| Generic type creation | O(1) | O(1) | Create parameterized type |
Basic Type Hints¶
Function Annotations¶
from typing import Optional, List
# Type hints - O(1) (no runtime overhead)
def greet(name: str) -> str:
return f"Hello, {name}"
# Optional type - O(1)
def find_user(user_id: int) -> Optional[str]:
# May return str or None
if user_id == 1:
return "Alice"
return None
# List type - O(1)
def sum_numbers(nums: List[int]) -> int:
return sum(nums)
# Hints are just annotations, no enforcement
greet(123) # Works! (type checker would complain)
Common Types¶
from typing import Dict, Tuple, Set, Union
# Dict - key and value types - O(1)
def process_config(config: Dict[str, int]) -> None:
pass
# Tuple - element types - O(1)
def get_coordinates() -> Tuple[float, float]:
return (0.0, 1.0)
# Set - element type - O(1)
def get_unique_ids(data: List[str]) -> Set[str]:
return set(data)
# Union - multiple possible types - O(1)
def parse_input(value: Union[str, int]) -> str:
return str(value)
Generic Types¶
Generic Functions¶
from typing import TypeVar, Generic
# TypeVar - represents any type - O(1)
T = TypeVar('T')
# Generic function - O(1)
def first(items: List[T]) -> T:
return items[0]
# Usage - preserves type
numbers = [1, 2, 3]
first_num: int = first(numbers) # Typed as int
strings = ["a", "b"]
first_str: str = first(strings) # Typed as str
Generic Classes¶
from typing import Generic, TypeVar
# Type variable - O(1)
T = TypeVar('T')
# Generic class - O(1)
class Stack(Generic[T]):
def __init__(self):
self.items: List[T] = []
def push(self, item: T) -> None: # O(1)
self.items.append(item)
def pop(self) -> T: # O(1)
return self.items.pop()
# Usage
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
Callable Types¶
Function Types¶
from typing import Callable
# Callable type - O(1)
def apply_operation(a: int, b: int, op: Callable[[int, int], int]) -> int:
return op(a, b)
# Pass functions - O(1)
def add(x: int, y: int) -> int:
return x + y
result = apply_operation(5, 3, add) # 8
Higher-Order Functions¶
from typing import Callable, List
# Return function - O(1)
def create_multiplier(factor: int) -> Callable[[int], int]:
def multiply(x: int) -> int:
return x * factor
return multiply
# Usage
times_three = create_multiplier(3) # O(1)
result = times_three(5) # 15
Union and Optional¶
Union Types¶
from typing import Union
# Union - multiple types - O(1)
def process_value(value: Union[int, str, float]) -> None:
if isinstance(value, int):
print(f"Integer: {value}")
elif isinstance(value, str):
print(f"String: {value}")
else:
print(f"Float: {value}")
# Works with any of the types
process_value(42) # Integer
process_value("hello") # String
process_value(3.14) # Float
Optional (Union with None)¶
from typing import Optional
# Optional[T] is Union[T, None] - O(1)
def find_item(items: List[str], target: str) -> Optional[str]:
try:
return items[items.index(target)]
except ValueError:
return None
# Usage
result = find_item(["a", "b", "c"], "b") # str or None
Type Aliases¶
Creating Type Aliases¶
from typing import List, Tuple, Dict
# Type alias - O(1)
Coordinate = Tuple[float, float]
Grid = List[List[int]]
Config = Dict[str, str]
# Use aliases - cleaner code
def get_location() -> Coordinate:
return (0.0, 1.0)
def create_grid() -> Grid:
return [[1, 2], [3, 4]]
# Aliases improve readability
def load_config(path: str) -> Config:
return {"key": "value"}
Type Hints in Practice¶
Class Annotations¶
from typing import List, Optional
class User:
# Class-level annotations - O(1)
name: str
age: int
email: Optional[str]
def __init__(self, name: str, age: int, email: Optional[str] = None):
self.name = name
self.age = age
self.email = email
def get_info(self) -> str:
return f"{self.name} ({self.age})"
# Usage
user = User("Alice", 30, "alice@example.com")
Method Annotations¶
from typing import List
class DataProcessor:
def __init__(self):
self.data: List[int] = []
def add_value(self, value: int) -> None:
self.data.append(value)
def get_sum(self) -> int:
return sum(self.data)
def get_average(self) -> float:
if not self.data:
return 0.0
return self.get_sum() / len(self.data)
Runtime Type Checking¶
Getting Type Hints¶
from typing import get_type_hints
def add(a: int, b: int) -> int:
return a + b
# Get hints - O(k) where k = number of hints
hints = get_type_hints(add) # O(1)
# Result: {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
# Check hint
if hints.get('a') == int:
print("Parameter 'a' is int")
Runtime Enforcement¶
from typing import get_type_hints
def validate_types(func):
"""Decorator to check types at runtime - O(k)"""
hints = get_type_hints(func) # O(k)
def wrapper(*args, **kwargs):
# Check arguments - O(n) where n = number of args
for i, arg in enumerate(args):
arg_names = list(hints.keys())
if i < len(arg_names):
arg_name = arg_names[i]
expected_type = hints[arg_name]
if not isinstance(arg, expected_type):
raise TypeError(f"{arg_name} must be {expected_type}")
return func(*args, **kwargs)
return wrapper
@validate_types
def add(a: int, b: int) -> int:
return a + b
add(5, 3) # OK
add(5, "3") # TypeError
Advanced Typing¶
Protocol (Structural Typing)¶
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("Drawing circle")
class Square:
def draw(self) -> None:
print("Drawing square")
def render(obj: Drawable) -> None:
obj.draw()
render(Circle()) # Works (implements draw)
render(Square()) # Works (implements draw)
Generic with Constraints¶
from typing import TypeVar
# TypeVar with constraints - O(1)
Number = TypeVar('Number', int, float)
def process_number(value: Number) -> Number:
return value * 2
process_number(5) # int
process_number(3.14) # float
# process_number("5") # Type error
Performance Considerations¶
Type Hints Have No Runtime Cost¶
# Type hints are annotations only (O(1) overhead)
def with_hints(x: int, y: int) -> int:
return x + y
def without_hints(x, y):
return x + y
# Both have identical performance
# Type hints don't add runtime overhead
Runtime Type Checking Can Be Expensive¶
# Simple check - O(1)
if isinstance(value, int):
pass
# Complex type check - O(n) for complex types
from typing import get_type_hints
hints = get_type_hints(func) # O(k) where k = number of hints
# Avoid repeated hint retrieval (cache it)
hints = get_type_hints(func) # O(k) once
for arg in args:
if isinstance(arg, hints['param']): # O(1) per check
pass
Best Practices¶
Code Examples¶
from typing import List, Optional, Dict, Callable
# Good: Clear types - O(1)
def process_data(
items: List[int],
processor: Callable[[int], int],
config: Optional[Dict[str, str]] = None
) -> List[int]:
"""Process items with given processor."""
return [processor(item) for item in items]
# Bad: No types
def process_data(items, processor, config=None):
"""Process items with given processor."""
return [processor(item) for item in items]
# Use type hints for better code documentation and IDE support
Version Notes¶
- Python 3.5+: Basic type hints support
- Python 3.6+: Variable annotations
- Python 3.9+: Can use
list[int]instead ofList[int] - Python 3.10+: Union with
|syntax:int | None
Related Modules¶
- pydantic - Runtime type validation (external)
- dataclasses - Type hints for data classes
- collections.abc - Abstract base classes
Best Practices¶
✅ Do:
- Use type hints for function parameters and returns
- Use
Optional[T]for nullable values - Use generic types for collections
- Use type aliases for complex types
- Include type hints in public APIs
❌ Avoid:
- Over-annotating (use when helpful)
- Assuming type hints are enforced (they're not)
- Using
Anyexcessively (defeats purpose) - Enforcing types at runtime (performance cost)
- Forgetting that hints are documentation only