Type Hinting
Type hinting lets you add type annotations to your Python code, making it clearer what types of values functions expect and return. While Python remains dynamically typed at runtime, type hints improve code documentation, enable better IDE autocomplete and error detection, and can be checked statically with tools like mypy. Introduced in PEP 484 (Python 3.5) and enhanced in later versions!
Basic Type Annotations
You can add type hints to function parameters and return values using the syntax
parameter: type and -> return_type. For simple types,
use built-in types like int, str, list,
dict.
Click Run to execute your code
- Documentation: Makes code self-documenting about expected types
- IDE Support: Better autocomplete, refactoring, and error detection
- Type Checking: Tools like mypy can catch type errors before runtime
- Refactoring: Easier to safely modify code when types are known
Note: Type hints don't affect runtime behavior - Python remains dynamically typed!
The typing Module
For more complex types, use the typing module. It provides generic
types like List, Dict, Optional,
Union, and more. Python 3.9+ allows using built-in types like
list and dict directly in type hints!
Click Run to execute your code
list[str] instead of List[str] from typing. Use
from __future__ import annotations at the top of files for Python
3.7+ to enable this syntax!
Optional and Union Types
Use Optional[Type] for values that can be None (same as
Union[Type, None]). Use Union[Type1, Type2] when a value
can be one of multiple types. Python 3.10+ supports the cleaner Type1 |
Type2 syntax!
Click Run to execute your code
Variable Type Annotations
You can also annotate variables directly, not just function parameters. This is useful for class attributes, module-level variables, and complex types that might not be obvious.
Click Run to execute your code
Common Mistakes
1. Thinking type hints enforce types at runtime
# Wrong - type hints don't prevent runtime errors
def add(a: int, b: int) -> int:
return a + b
# This still works - Python doesn't check types at runtime!
result = add("hello", "world") # No error, returns "helloworld"
result = add([1, 2], [3, 4]) # No error, returns [1, 2, 3, 4]
# Type hints are for documentation and static type checkers only
# Use mypy or other tools to catch these before runtime!
2. Using old typing.List syntax when not needed
# Old way (Python 3.8 and earlier)
from typing import List, Dict
def process(items: List[str]) -> Dict[str, int]:
return {}
# Modern way (Python 3.9+)
def process(items: list[str]) -> dict[str, int]:
return {}
# Or for Python 3.7+ with future import
from __future__ import annotations
def process(items: list[str]) -> dict[str, int]:
return {}
3. Forgetting Optional for None-able values
# Wrong - suggests value can't be None
from typing import Optional
def find_user(name: str) -> dict: # Type checker might complain
if name not in database:
return None # Type error: returning None but hint says dict
return {"name": name}
# Correct - use Optional
def find_user(name: str) -> Optional[dict]:
if name not in database:
return None # OK - Optional allows None
return {"name": name}
Exercise: Add Type Hints to a Function
Task: Write a function with comprehensive type hints that processes user data. The function should accept a list of user dictionaries and return statistics.
Requirements:
- Create a function
get_user_stats(users)that processes user data - Parameter: list of dictionaries, each with
name(str) andage(int) - Return: dictionary with keys
total(int),avg_age(float), andnames(list of str) - Use type hints for all parameters and return value
- Import necessary types from
typingmodule - Handle empty list case
Click Run to execute your code
Show Solution
from typing import List, Dict, Any
def get_user_stats(users: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Calculate statistics from a list of user dictionaries.
Args:
users: List of user dicts with 'name' (str) and 'age' (int)
Returns:
Dict with 'total' (int), 'avg_age' (float), 'names' (List[str])
"""
if not users:
return {"total": 0, "avg_age": 0.0, "names": []}
total = len(users)
ages = [user["age"] for user in users]
avg_age = sum(ages) / len(ages)
names = [user["name"] for user in users]
return {
"total": total,
"avg_age": avg_age,
"names": names
}
# Test the function
users = [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 30},
{"name": "Charlie", "age": 35}
]
stats = get_user_stats(users)
print(f"Total users: {stats['total']}")
print(f"Average age: {stats['avg_age']:.1f}")
print(f"Names: {', '.join(stats['names'])}")
Summary
- Type Hints: Annotations that document expected types without affecting runtime behavior
- Function Annotations: Use
parameter: typeand-> return_typesyntax - Built-in Types: Use
int,str,list,dictfor simple types (Python 3.9+) - typing Module: Provides
List,Dict,Optional,Unionfor complex types - Optional:
Optional[Type]meansType | None, for values that can be None - Union:
Union[Type1, Type2]orType1 | Type2(Python 3.10+) for multiple possible types - Variable Annotations: Can annotate variables directly:
name: str = "value" - Static Checking: Use tools like mypy to validate type hints before runtime
- Benefits: Better documentation, IDE support, easier refactoring, catch errors early
What's Next?
Type hinting helps make your code more maintainable and self-documenting! You've now completed all the Advanced Topics modules. Continue exploring Python by diving deeper into specific libraries, frameworks, or advanced patterns. Consider learning about virtual environments, package management, async programming, or contributing to open source projects to further your Python journey!
Enjoying these tutorials?