Python’s typing Library

Python is a dynamically typed language. This means you don’t declare the type of a variable (x = 5 doesn’t say x must be an int). You can even change a variable’s type: x = 5, then x = "hello". This offers flexibility but can lead to confusion or errors, especially in larger codebases or when working in teams.

The typing library (and the concept of type hints) allows you to annotate your code with expected types. These annotations are not enforced at runtime by default (Python still runs as dynamically typed).

Prompt: “Generate a single Python script that demonstrates the following typing constructs: basic types, List, Dict, Union, Optional, Any, Callable, TypeAlias, TypeVar, and TypedDict. Include comments explaining each one, and show a simple mypy check.

# AI-Generated Python Code Example

# 1. Essential Imports
from typing import (
    List, Dict, Union, Optional, Any, Callable, TypeAlias, TypeVar, TypedDict
)

# --- Basic Type Hints ---
# Simple types for variables, function parameters, and return values
def greet(name: str) -> str:
    return f"Hello, {name}!"

age: int = 30
is_active: bool = True
price: float = 9.99

# --- Collection Type Hints (Generic Types) ---
# Use uppercase types from 'typing' for collections when specifying contents
user_ids: List[int] = [101, 102, 103]
user_data: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
config_settings: Dict[str, Any] = {"debug": True, "api_key": "abc123xyz", "retries": 3} # Value can be anything

# --- Union: For multiple possible types ---
# Use Union[Type1, Type2, ...] if a variable/parameter can be one of several types
def process_id(user_id: Union[int, str]) -> str:
    if isinstance(user_id, int):
        return f"Processing integer ID: {user_id}"
    return f"Processing string ID: {user_id.upper()}"

# --- Optional: For values that can be a type OR None ---
# Optional[X] is syntactic sugar for Union[X, None]
def get_user_email(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "test@example.com"
    return None # Return None if user not found

# --- Any: For when you genuinely don't know or don't care about the type ---
# Use sparingly! It effectively turns off type checking for that specific annotation.
def debug_print(data: Any) -> None:
    print(f"Debug: {data} (type: {type(data)})")

# --- Callable: For hinting functions/callbacks ---
# Callable[[Arg1Type, Arg2Type], ReturnType]
def apply_operation(value: int, operation: Callable[[int], int]) -> int:
    return operation(value)

def double_num(x: int) -> int:
    return x * 2

# --- TypeAlias: For creating custom names for complex types ---
# Improves readability and reduces repetition
Coordinates: TypeAlias = Dict[str, float] # Example: {"lat": 40.7, "lon": -74.0}
SensorReading: TypeAlias = Dict[str, Union[int, float, str]] # Example: {"temp": 25.5, "unit": "C"}

def log_coords(coords: Coordinates) -> None:
    print(f"Logging coordinates: Lat {coords['lat']}, Lon {coords['lon']}")

# --- TypeVar: For creating generic functions or classes ---
# Allows you to define a type that is consistent across different parts of a function/class
T = TypeVar('T') # Declare a TypeVar named 'T'

def first_element(items: List[T]) -> T:
    """Returns the first element of a list, maintaining its original type."""
    return items[0]

# --- TypedDict: For dictionaries with a known set of keys and types ---
# Provides more precise type checking for dictionary structures than Dict[str, Any]
class UserProfile(TypedDict):
    name: str
    age: int
    email: Optional[str]
    is_admin: bool

def process_user_profile(profile: UserProfile) -> None:
    print(f"Processing user: {profile['name']}, Age: {profile['age']}")
    if profile['is_admin']:
        print("  (Administrator account)")

# --- Demonstrate Usage ---
if __name__ == "__main__":
    print(greet("World"))
    print(f"Age: {age}, Active: {is_active}, Price: {price}")

    print(f"User IDs: {user_ids}")
    print(f"User data: {user_data}")
    print(f"Config settings: {config_settings}")

    print(process_id(123))
    print(process_id("ABC"))

    print(f"Email for user 1: {get_user_email(1)}")
    print(f"Email for user 99: {get_user_email(99)}")

    debug_print("Hello from debug_print")
    debug_print([1, 2, 3])

    result = apply_operation(10, double_num)
    print(f"Result of applying double_num: {result}")

    my_coords: Coordinates = {"lat": 34.5, "lon": -118.0}
    log_coords(my_coords)

    int_list = [1, 2, 3]
    str_list = ["a", "b", "c"]
    print(f"First element of int_list: {first_element(int_list)}")
    print(f"First element of str_list: {first_element(str_list)}")

    user1: UserProfile = {"name": "Charlie", "age": 28, "email": "charlie@example.com", "is_admin": False}
    user2: UserProfile = {"name": "Diana", "age": 35, "is_admin": True, "email": None} # email can be None
    process_user_profile(user1)
    process_user_profile(user2)

    # --- Mypy Check Demonstration ---
    print("\n--- To check this file with MyPy: ---")
    print("1. Save the code as 'type_example.py'")
    print("2. Install mypy: `pip install mypy`")
    print("3. Run mypy from your terminal: `mypy type_example.py`")
    print("\n--- Example of a type error MyPy would catch (uncomment to test): ---")
    # incorrect_age: str = 30 # MyPy: Incompatible types in assignment (expression has type "int", variable has type "str")
    # invalid_user_profile: UserProfile = {"name": "Eve", "age": "thirty", "is_admin": False} # MyPy: Value for "age" of "UserProfile" has incompatible type "str"; expected "int"
    # log_coords({"latitude": 10.0, "longitude": 20.0}) # MyPy: Dict entry "lat" missing for "Coordinates"

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.