Web Analytics

Type Hinting

Advanced ~25 min read

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.

Output
Click Run to execute your code
Why Use Type Hints:
- 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!

Output
Click Run to execute your code
Pro Tip: In Python 3.9+, you can use built-in types like 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!

Output
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.

Output
Click Run to execute your code
Important: Type hints are not enforced at runtime! Python will still allow you to pass any type. Type hints are primarily for documentation and static type checking. Use type checkers like mypy to catch type errors before runtime!

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) and age (int)
  • Return: dictionary with keys total (int), avg_age (float), and names (list of str)
  • Use type hints for all parameters and return value
  • Import necessary types from typing module
  • Handle empty list case
Output
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: type and -> return_type syntax
  • Built-in Types: Use int, str, list, dict for simple types (Python 3.9+)
  • typing Module: Provides List, Dict, Optional, Union for complex types
  • Optional: Optional[Type] means Type | None, for values that can be None
  • Union: Union[Type1, Type2] or Type1 | 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!