Skip to main content Brad's PyNotes

PEP 484: Type Hints - Static Type Checking for Python

TL;DR

PEP 484 introduced type hints to Python, enabling static type checking with tools like mypy while maintaining runtime flexibility and improving code documentation and IDE support.

Interesting!

Type hints are completely optional and ignored at runtime - Python remains dynamically typed, but type checkers can catch potential bugs before your code even runs!

Basic Type Hints

python code snippet start

def greet(name: str, age: int) -> str:
    return f"Hello {name}, you are {age} years old"

# Variables can be annotated too
count: int = 0
message: str = "Hello, World!"
is_active: bool = True

# Function with no return value
def log_message(message: str) -> None:
    print(f"LOG: {message}")

python code snippet end

Collection Types

python code snippet start

from typing import List, Dict, Set, Tuple, Optional

# Collection types
numbers: List[int] = [1, 2, 3, 4]
user_data: Dict[str, int] = {"age": 30, "score": 95}
tags: Set[str] = {"python", "typing", "pep484"}
coordinates: Tuple[float, float] = (3.14, 2.71)

# Optional types (can be None)
def find_user(user_id: int) -> Optional[str]:
    users = {1: "Alice", 2: "Bob"}
    return users.get(user_id)  # Returns str or None

python code snippet end

Union Types and Generics

python code snippet start

from typing import Union, Generic, TypeVar

# Union types - can be one of several types
def process_id(user_id: Union[int, str]) -> str:
    return str(user_id)

# Generic types
T = TypeVar('T')

def first_item(items: List[T]) -> Optional[T]:
    return items[0] if items else None

# Usage preserves type information
first_number = first_item([1, 2, 3])  # Type: Optional[int]
first_word = first_item(["a", "b"])   # Type: Optional[str]

python code snippet end

Class Type Hints

python code snippet start

from typing import ClassVar
from datetime import datetime

class User:
    # Class variable
    total_users: ClassVar[int] = 0
    
    def __init__(self, name: str, email: str) -> None:
        self.name: str = name
        self.email: str = email
        self.created_at: datetime = datetime.now()
        User.total_users += 1
    
    def get_info(self) -> Dict[str, str]:
        return {
            "name": self.name,
            "email": self.email
        }
    
    @classmethod
    def create_admin(cls, name: str) -> 'User':
        return cls(name, f"{name}@admin.com")

python code snippet end

Advanced Type Hints

python code snippet start

from typing import Callable, Protocol, Literal

# Function types
def apply_operation(x: int, operation: Callable[[int], int]) -> int:
    return operation(x)

# Usage
result = apply_operation(5, lambda x: x * 2)

# Protocols (structural typing)
class Drawable(Protocol):
    def draw(self) -> None: ...

def render(obj: Drawable) -> None:
    obj.draw()  # Any object with a draw() method works

# Literal types
def set_mode(mode: Literal["debug", "production"]) -> None:
    print(f"Setting mode to {mode}")

set_mode("debug")      # OK
set_mode("testing")    # Type checker error

python code snippet end

Type Checking with mypy

bash code snippet start

# Install mypy
pip install mypy

# Check your code
mypy your_script.py

bash code snippet end

python code snippet start

# Example with type errors
def add_numbers(a: int, b: int) -> int:
    return a + b

result = add_numbers("5", "10")  # mypy error: incompatible types

python code snippet end

Type hints make Python code more self-documenting and help catch errors early in development.

Type hints evolved further with PEP 526 (Variable Annotations) and integrate seamlessly with Python's class system for comprehensive type safety. Modern Python extends type hints with built-in generic collections and union operator syntax for cleaner annotations. Advanced typing features include positional-only parameters for more precise function signatures and structural subtyping with protocols for duck typing with type safety.

Reference: PEP 484 - Type Hints