From Beginner to Expert

    Table of Contents

    1. Quick Start Guide
    2. Introduction to Python Typing
    3. Basic Type Hints
    4. Collection Types
    5. Advanced Types
    6. Generic Types
    7. Protocols and Structural Typing
    8. Type Checking Tools
    9. Best Practices
    10. Common Pitfalls and Troubleshooting
    11. Real-World Examples
    12. Learning Roadmap

    1. Quick Start Guide

    ๐Ÿš€ Your First 5 Minutes with Python Typing

    Prerequisites: Python 3.6+ (Recommended: Python 3.9+)

    Step 1: Setup Your Environment

    # Install type checker
    pip install mypy
    
    # For VS Code users (recommended)
    # Install Python extension and Pylance
    Bash

    Step 2: Your First Type Hints

    Create a file hello_typing.py:

    def greet(name: str, age: int) -> str:
        return f"Hello {name}, you are {age} years old!"
    
    def calculate_discount(price: float, discount_percent: float) -> float:
        return price * (1 - discount_percent / 100)
    
    # Try it out
    message = greet("Alice", 25)
    discounted_price = calculate_discount(100.0, 10.0)
    print(message)
    print(f"Discounted price: ${discounted_price}")
    Python

    Step 3: Run Type Checking

    # Check for type errors
    mypy hello_typing.py
    
    # Should output: Success: no issues found in 1 source file
    Bash

    Step 4: See Type Checking in Action

    Add this line to your file and run mypy again:

    # This will cause a type error
    wrong_greeting = greet(123, "Alice")  # Arguments swapped!
    Python
    mypy hello_typing.py
    # Output: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
    Bash

    ๐ŸŽฏ Quick Reference Card

    TypeExampleUsage
    strname: str = "Alice"Text strings
    intage: int = 25Whole numbers
    floatprice: float = 19.99Decimal numbers
    boolis_active: bool = TrueTrue/False values
    list[int]numbers: list[int] = [1, 2, 3]List of integers
    dict[str, int]scores: dict[str, int] = {"Alice": 95}String to int mapping
    Optional[str]middle_name: Optional[str] = NoneCan be None

    ๐Ÿ’ก Pro Tips for Beginners

    1. Start Small: Add types to function signatures first
    2. Use Your IDE: Modern IDEs show type errors as you type
    3. Don’t Panic: Type errors won’t crash your program at runtime
    4. Gradual Adoption: You can add types to existing code incrementally

    2. Introduction to Python Typing

    What is Type Hinting?

    Type hinting is a feature introduced in Python 3.5 that allows you to specify the expected types of variables, function parameters, and return values. It makes code more readable, helps catch bugs early, and enables better IDE support.

    ๐Ÿ”‘ Key Insight: Type hints are annotations, not enforcement. Python remains dynamically typed at runtime, but type hints provide static analysis benefits.

    graph TD
        A[๐Ÿ“ Python Code] --> B{๐Ÿท๏ธ Type Hints?}
        B -->|โœ… Yes| C[๐Ÿ” Better IDE Support]
        B -->|โœ… Yes| D[๐Ÿ›ก๏ธ Static Type Checking]
        B -->|โœ… Yes| E[๐Ÿ“š Documentation]
        B -->|โŒ No| F[๐Ÿƒ Runtime Only]
        C --> G[๐Ÿ’ก Autocomplete]
        C --> H[๐Ÿšจ Error Detection]
        D --> I[๐Ÿ”ง mypy/pyright]
        E --> J[๐Ÿ“– Self-Documenting Code]
    
        style A fill:#e1f5fe
        style C fill:#e8f5e8
        style D fill:#e8f5e8
        style E fill:#e8f5e8
        style F fill:#ffebee

    Duck Typing vs Static Typing

    graph LR
        subgraph "๐Ÿฆ† Duck Typing (Traditional Python)"
            A1["if it walks like a duckand quacks like a duckthen it's a duck"] --> B1[Runtime Discovery]
            B1 --> C1[Flexible but Error-Prone]
        end
    
        subgraph "๐Ÿ“‹ Static Typing (Modern Python)"
            A2[Explicit Type Declarations] --> B2[Compile-time Checking]
            B2 --> C2[Structured but Safe]
        end
    
        style A1 fill:#fff3e0
        style A2 fill:#e3f2fd

    Why Use Type Hints?

    1. ๐Ÿ“– Code Documentation: Types serve as inline documentation that never goes out of sync
    2. ๐Ÿ”ง IDE Support: Better autocomplete, refactoring, and error detection
    3. ๐Ÿ› Bug Prevention: Catch type-related errors before runtime
    4. ๐Ÿ”„ Refactoring Safety: Easier to refactor with confidence
    5. ๐Ÿ‘ฅ Team Collaboration: Clearer contracts between team members
    6. ๐Ÿš€ Performance: Some tools can optimize typed code better

    The Cost-Benefit Analysis

    graph TD
        subgraph "๐Ÿ’ฐ Costs"
            A[Learning Curve]
            B[Initial Setup Time]
            C[More Verbose Code]
        end
    
        subgraph "๐Ÿ’Ž Benefits"
            D[Fewer Runtime Errors]
            E[Better IDE Experience]
            F[Easier Maintenance]
            G[Team Productivity]
            H[Documentation]
        end
    
        A --> I{Worth It?}
        B --> I
        C --> I
        D --> I
        E --> I
        F --> I
        G --> I
        H --> I
    
        I -->|For most projects| J[โœ… YES]
    
        style J fill:#e8f5e8

    Evolution of Python Typing

    timeline
        title ๐Ÿ“ˆ Python Typing Evolution
        2014 : PEP 484 : ๐Ÿ’ก Type Hints Proposal
        2015 : Python 3.5 : ๐ŸŽฏ typing module introduced
        2016 : Python 3.6 : ๐Ÿ“ Variable annotations (PEP 526)
        2017 : Python 3.7 : ๐Ÿ”ฎ Postponed evaluation of annotations
        2019 : Python 3.8 : ๐Ÿท๏ธ Literal types, TypedDict, Final
        2020 : Python 3.9 : ๐Ÿ—๏ธ Built-in generic types (list[int])
        2021 : Python 3.10 : โšก Union operator (int | str)
        2022 : Python 3.11 : ๐Ÿ”„ Self type, exception groups
        2023 : Python 3.12 : ๐ŸŽญ Generic type syntax

    Migration Timeline Recommendation

    graph LR
        A[Legacy Code] --> B[Add Return Types]
        B --> C[Add Parameter Types]
        C --> D[Add Variable Types]
        D --> E[Enable Strict Mode]
        E --> F[Full Type Coverage]
    
        style A fill:#ffebee
        style F fill:#e8f5e8

    3. Basic Type Hints

    Primitive Types

    # โœ… Basic type annotations
    def greet(name: str) -> str:
        return f"Hello, {name}!"
    
    def add_numbers(a: int, b: int) -> int:
        return a + b
    
    def calculate_average(numbers: list[float]) -> float:  # More specific than just 'list'
        if not numbers:  # Handle edge case
            raise ValueError("Cannot calculate average of empty list")
        return sum(numbers) / len(numbers)
    
    # โœ… Variable annotations (Python 3.6+)
    age: int = 25
    is_student: bool = True
    height: float = 5.9
    name: str  # Declaration without assignment
    name = "Alice"  # Assignment later
    Python

    ๐Ÿšจ Common Beginner Mistakes

    # โŒ Don't do this
    def bad_function(numbers: list) -> float:  # Too generic
        return sum(numbers) / len(numbers)
    
    def another_bad_function(data):  # Missing type hints
        return data * 2
    
    # โœ… Do this instead
    def good_function(numbers: list[float]) -> float:  # Specific type
        if not numbers:
            return 0.0
        return sum(numbers) / len(numbers)
    
    def another_good_function(data: int) -> int:  # Clear type hints
        return data * 2
    Python

    ๐Ÿ’ก Type Annotation Best Practices

    graph TD
        A[๐Ÿ“ Writing Type Hints] --> B{Choose Specificity Level}
        B -->|Too Generic| C["โŒ list, dict, Any"]
        B -->|Just Right| D["โœ… list[str], dict[str, int]"]
        B -->|Too Specific| E["โŒ Literal values only"]
    
        D --> F["๐ŸŽฏ Balanced Approach"]
    
        style C fill:#ffebee
        style D fill:#e8f5e8
        style E fill:#fff3e0
        style F fill:#e8f5e8

    Type Hierarchy and Compatibility

    graph TD
        A[๐ŸŒ object] --> B[๐Ÿ”ข int]
        A --> C[๐Ÿ“Š float]
        A --> D[๐Ÿ“ str]
        A --> E[โœ… bool]
        A --> F[๐Ÿงฎ complex]
        A --> G[๐Ÿ“ฆ bytes]
        A --> H[๐Ÿ”„ bytearray]
    
        B --> E
        B -.->|can be used as| C
    
        A --> I[๐Ÿ“‹ Sequence]
        I --> J[๐Ÿ“Œ list]
        I --> K[๐Ÿ”’ tuple]
        I --> L[๐Ÿ“ str]
    
        A --> M[๐Ÿ—บ๏ธ Mapping]
        M --> N[๐Ÿ“š dict]
    
        style A fill:#e3f2fd
        style B fill:#fff3e0
        style C fill:#fff3e0
        style E fill:#e8f5e8

    ๐Ÿ”„ Type Compatibility Rules

    # โœ… These work due to type hierarchy
    def process_number(value: float) -> float:
        return value * 2
    
    result1 = process_number(5)      # int -> float โœ…
    result2 = process_number(5.5)    # float -> float โœ…
    result3 = process_number(True)   # bool -> int -> float โœ…
    
    # โŒ These don't work
    # result4 = process_number("5")    # str -> float โŒ
    Python

    None and Optional Types

    from typing import Optional, Union
    
    # โœ… Optional type (can be None)
    def find_user(user_id: int) -> Optional[str]:
        """
        Find a user by ID. Returns username or None if not found.
        Optional[str] is equivalent to Union[str, None]
        """
        users = {1: "Alice", 2: "Bob", 3: "Charlie"}
        return users.get(user_id)  # dict.get() returns None if key not found
    
    # โœ… Union type (multiple possible types)
    def process_id(user_id: Union[int, str]) -> str:
        """Process user ID that can be either int or string."""
        return str(user_id)
    
    # ๐Ÿ†• Python 3.10+ syntax (preferred)
    def modern_process_id(user_id: int | str) -> str:
        """Modern union syntax using | operator."""
        return str(user_id)
    
    def modern_find_user(user_id: int) -> str | None:
        """Modern optional syntax."""
        users = {1: "Alice", 2: "Bob", 3: "Charlie"}
        return users.get(user_id)
    
    # ๐Ÿ” Practical example with error handling
    def safe_divide(a: float, b: float) -> Optional[float]:
        """Safely divide two numbers, return None if division by zero."""
        if b == 0:
            return None
        return a / b
    
    # Usage
    result = safe_divide(10, 2)
    if result is not None:
        print(f"Result: {result}")
    else:
        print("Division by zero!")
    Python

    ๐Ÿ’ก Optional vs Union Guidelines

    graph TD
        A[Need to represent absence?] --> B{What kind?}
        B -->|Missing value| C["Use Optional[T]"]
        B -->|Multiple types| D["Use Union[T, U]"]
        B -->|Error state| E[Consider exceptions]
    
        C --> F["โœ… Optional[str]"]
        D --> G["โœ… Union[int, str]"]
        E --> H["๐Ÿค” Raise ValueError"]
    
        style F fill:#e8f5e8
        style G fill:#e8f5e8
        style H fill:#fff3e0

    Type Aliases for Better Readability

    from typing import Union, List, Dict, NewType
    
    # โœ… Simple type aliases for domain concepts
    UserId = int
    UserName = str
    Email = str
    UserData = Dict[str, Union[str, int, bool]]
    UserList = List[UserData]
    
    # ๐Ÿš€ Advanced: NewType for stronger typing
    UserId = NewType('UserId', int)  # Creates a distinct type
    ProductId = NewType('ProductId', int)
    
    def get_user(user_id: UserId) -> UserData:
        """Get user by ID. UserId provides type safety."""
        # Implementation here
        pass
    
    def get_product(product_id: ProductId) -> dict:
        """Get product by ID. ProductId prevents mixing with UserId."""
        # Implementation here
        pass
    
    # โœ… This works
    user_id = UserId(123)
    user = get_user(user_id)
    
    # โŒ This would cause a type error
    product_id = ProductId(456)
    # user = get_user(product_id)  # Type error!
    
    # ๐Ÿ“Š Complex type aliases for data structures
    Coordinates = tuple[float, float]
    BoundingBox = tuple[Coordinates, Coordinates]
    Color = tuple[int, int, int]  # RGB values
    
    # ๐Ÿ—บ๏ธ Business domain aliases
    PriceData = Dict[str, Union[float, str]]  # {"amount": 19.99, "currency": "USD"}
    OrderItem = Dict[str, Union[str, int, float]]
    ShoppingCart = List[OrderItem]
    
    def calculate_cart_total(cart: ShoppingCart) -> float:
        """Calculate total price of items in cart."""
        return sum(item.get("price", 0.0) for item in cart)
    
    def create_user(user_id: UserId, name: UserName, email: Email) -> UserData:
        """Create a new user with type-safe parameters."""
        return {
            "id": user_id,
            "name": name,
            "email": email,
            "active": True,
            "created_at": "2025-09-18"
        }
    
    def get_all_users() -> UserList:
        """Get all users from the system."""
        return [
            {"id": UserId(1), "name": "Alice", "email": "alice@example.com", "active": True},
            {"id": UserId(2), "name": "Bob", "email": "bob@example.com", "active": False}
        ]
    Python

    ๐ŸŽฏ When to Use Type Aliases

    graph TD
        A[Complex Type?] --> B{How complex?}
        B -->|Simple| C[Consider alias]
        B -->|Complex| D[Definitely use alias]
        B -->|Used often?| E[Use alias]
    
        C --> F{Domain concept?}
        F -->|Yes| G[โœ… Use NewType]
        F -->|No| H[โœ… Use simple alias]
    
        D --> I[โœ… Use alias + documentation]
        E --> J[โœ… Use alias for DRY]
    
        style G fill:#e8f5e8
        style H fill:#e8f5e8
        style I fill:#e8f5e8
        style J fill:#e8f5e8

    ๐ŸŽฏ Practice Exercise: Basic Types

    Challenge: Create a simple calculator with proper type hints.

    # TODO: Add type hints to this calculator
    def add(a, b):
        return a + b
    
    def divide(a, b):
        if b == 0:
            return None
        return a / b
    
    def calculate_tip(bill_amount, tip_percentage):
        return bill_amount * (tip_percentage / 100)
    
    def format_currency(amount, currency="USD"):
        return f"{amount:.2f} {currency}"
    
    # TODO: Use the functions with type checking
    result1 = add(10, 5)
    result2 = divide(10, 3)
    result3 = calculate_tip(50.0, 15)
    formatted = format_currency(result3)
    Python

    Solution:

    from typing import Optional
    
    def add(a: float, b: float) -> float:
        """Add two numbers."""
        return a + b
    
    def divide(a: float, b: float) -> Optional[float]:
        """Divide two numbers, return None if division by zero."""
        if b == 0:
            return None
        return a / b
    
    def calculate_tip(bill_amount: float, tip_percentage: float) -> float:
        """Calculate tip amount based on bill and percentage."""
        return bill_amount * (tip_percentage / 100)
    
    def format_currency(amount: float, currency: str = "USD") -> str:
        """Format amount as currency string."""
        return f"{amount:.2f} {currency}"
    
    # Usage with type checking
    result1: float = add(10.0, 5.0)
    result2: Optional[float] = divide(10.0, 3.0)
    result3: float = calculate_tip(50.0, 15.0)
    formatted: str = format_currency(result3)
    
    print(f"Addition: {result1}")
    print(f"Division: {result2}")
    print(f"Tip: {formatted}")
    Python

    4. Collection Types

    Lists, Tuples, and Sets

    from typing import List, Tuple, Set, Dict  # Legacy imports (Python < 3.9)
    
    # โœ… Homogeneous collections - all elements same type
    numbers: List[int] = [1, 2, 3, 4, 5]
    names: Set[str] = {"Alice", "Bob", "Charlie"}
    grades: Dict[str, float] = {"Alice": 95.5, "Bob": 87.2}
    
    # โœ… Heterogeneous tuples - fixed structure
    person: Tuple[str, int, bool] = ("Alice", 30, True)  # name, age, is_active
    coordinate_2d: Tuple[float, float] = (10.5, 20.3)   # x, y
    rgb_color: Tuple[int, int, int] = (255, 128, 0)     # red, green, blue
    
    # โœ… Variable-length tuples - same type, variable count
    coordinates: Tuple[float, ...] = (1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
    test_scores: Tuple[int, ...] = (85, 92, 78, 96)
    
    # โŒ Common mistakes
    mixed_list: List = [1, "two", 3.0]  # Too generic!
    any_dict: Dict = {"key": "value"}   # Too generic!
    
    # โœ… Better alternatives
    mixed_data: List[Union[int, str, float]] = [1, "two", 3.0]  # Explicit union
    config_dict: Dict[str, str] = {"host": "localhost", "port": "8080"}
    Python

    ๐Ÿ†• Modern Collection Syntax (Python 3.9+)

    # ๐Ÿš€ Python 3.9+ - using built-in types directly (preferred!)
    def process_numbers(numbers: list[int]) -> list[int]:
        """Double all numbers in the list."""
        return [n * 2 for n in numbers]
    
    def group_by_category(items: list[str]) -> dict[str, list[str]]:
        """Group items by their first letter."""
        result: dict[str, list[str]] = {}
        for item in items:
            category = item[0].upper()
            if category not in result:
                result[category] = []
            result[category].append(item)
        return result
    
    # ๐ŸŽฏ Real-world examples
    def calculate_student_averages(grades: dict[str, list[float]]) -> dict[str, float]:
        """Calculate average grade for each student."""
        return {
            student: sum(student_grades) / len(student_grades)
            for student, student_grades in grades.items()
            if student_grades  # Avoid division by zero
        }
    
    # ๐Ÿ“Š Complex nested structures
    UserPreferences = dict[str, dict[str, bool]]  # user_id -> preference -> enabled
    ProductCatalog = dict[str, list[tuple[str, float]]]  # category -> [(name, price), ...]
    
    def get_user_notifications(
        preferences: UserPreferences, 
        user_id: str
    ) -> list[str]:
        """Get enabled notification types for a user."""
        user_prefs = preferences.get(user_id, {})
        return [notif_type for notif_type, enabled in user_prefs.items() if enabled]
    Python

    Collection Hierarchy and When to Use Each

    graph TD
        A[๐Ÿ“š Collection] --> B[๐Ÿ“‹ Sequence]
        A --> C[๐ŸŽฏ Set]
        A --> D[๐Ÿ—บ๏ธ Mapping]
        A --> E[๐Ÿ”„ Iterator]
    
        B --> F[๐Ÿ“Œ list - Ordered, Mutable]
        B --> G[๐Ÿ”’ tuple - Ordered, Immutable]
        B --> H[๐Ÿ“ str - Ordered, Immutable]
        B --> I[๐Ÿ“ฆ bytes - Ordered, Immutable]
    
        C --> J[๐Ÿ”ง set - Unordered, Mutable]
        C --> K[โ„๏ธ frozenset - Unordered, Immutable]
    
        D --> L["๐Ÿ“š dict - Key-Value, Ordered (3.7+)"]
    
        E --> M[๐ŸŒŠ Generator]
        E --> N[๐Ÿ” Iterator]
    
        style F fill:#e8f5e8
        style G fill:#fff3e0
        style H fill:#e3f2fd
        style J fill:#ffebee
        style L fill:#f3e5f5

    ๐ŸŽฏ Collection Selection Guide

    Use CaseCollection TypeExample
    ๐Ÿ“ Ordered items, need indexinglist[T]Shopping cart items
    ๐Ÿ”’ Fixed structure, immutabletuple[T, ...]Coordinates, RGB values
    ๐ŸŽฏ Unique items, fast lookupset[T]User permissions, tags
    ๐Ÿ—บ๏ธ Key-value relationshipsdict[K, V]User profiles, config
    ๐ŸŒŠ Large datasets, memory efficientIterator[T]File processing

    ๐Ÿš€ Advanced Collection Examples

    from typing import DefaultDict, Counter, Deque, ChainMap
    from collections import defaultdict, Counter, deque, ChainMap
    
    # ๐Ÿ“Š Specialized collections with proper typing
    word_count: Counter[str] = Counter(["apple", "banana", "apple", "cherry", "apple"])
    grouped_items: DefaultDict[str, list[int]] = defaultdict(list)
    processing_queue: Deque[str] = deque(["task1", "task2", "task3"])
    
    # ๐Ÿ” Practical text analysis function
    def analyze_text(text: str) -> dict[str, Union[int, Counter[str], set[str]]]:
        """Comprehensive text analysis with proper typing."""
        words = text.lower().split()
        return {
            "word_count": len(words),
            "unique_words": len(set(words)),
            "character_count": len(text),
            "frequency": Counter(words),
            "unique_word_set": set(words)
        }
    
    # ๐Ÿ—๏ธ Advanced data structures
    class DataProcessor:
        def __init__(self) -> None:
            self.data_cache: dict[str, list[dict[str, Any]]] = {}
            self.counters: defaultdict[str, Counter[str]] = defaultdict(Counter)
            self.processing_queue: deque[tuple[str, dict[str, Any]]] = deque()
    
        def add_data(self, category: str, item: dict[str, Any]) -> None:
            """Add data item to specific category."""
            if category not in self.data_cache:
                self.data_cache[category] = []
            self.data_cache[category].append(item)
    
            # Update counters for this category
            if "type" in item:
                self.counters[category][item["type"]] += 1
    
            # Add to processing queue
            self.processing_queue.append((category, item))
    
        def get_statistics(self) -> dict[str, dict[str, int]]:
            """Get statistics for all categories."""
            return {
                category: dict(counter) 
                for category, counter in self.counters.items()
            }
    
    # ๐ŸŒ Configuration management with ChainMap
    default_config: dict[str, str] = {
        "host": "localhost",
        "port": "8080",
        "debug": "false"
    }
    
    user_config: dict[str, str] = {
        "host": "production.example.com",
        "debug": "true"
    }
    
    # ChainMap for configuration precedence
    config_chain: ChainMap[str, str] = ChainMap(user_config, default_config)
    
    def get_config_value(key: str) -> str:
        """Get configuration value with fallback to defaults."""
        return config_chain.get(key, "")
    
    # ๐Ÿ“ˆ Performance-optimized data processing
    def process_large_dataset(
        data: Iterator[dict[str, Any]]
    ) -> Generator[dict[str, Any], None, None]:
        """Process large dataset without loading everything into memory."""
        for item in data:
            if item.get("is_valid", False):
                # Transform the item
                processed_item = {
                    "id": item.get("id"),
                    "processed_at": datetime.now().isoformat(),
                    "value": item.get("value", 0) * 2
                }
                yield processed_item
    Python

    โšก Performance Tips for Collections

    graph TD
        A[Collection Performance] --> B[Memory Usage]
        A --> C[Access Speed]
        A --> D[Iteration Speed]
    
        B --> E[Use generators for large data]
        B --> F[Choose appropriate collection type]
    
        C --> G["dict/set: O(1) lookup"]
        C --> H["list: O(1) index, O(n) search"]
    
        D --> I[Avoid repeated conversions]
        D --> J[Use comprehensions]
    
        style E fill:#e8f5e8
        style F fill:#e8f5e8
        style G fill:#e8f5e8
        style H fill:#fff3e0
        style I fill:#e8f5e8
        style J fill:#e8f5e8

    ๐ŸŽฏ Collection Exercise: Data Analysis

    Challenge: Build a data analysis system with proper typing.

    # TODO: Add proper type hints and implement the functions
    def load_sales_data(filename):
        # Load CSV data and return list of sales records
        pass
    
    def group_sales_by_month(sales_data):
        # Group sales by month and return dictionary
        pass
    
    def calculate_monthly_totals(grouped_sales):
        # Calculate total sales for each month
        pass
    
    def find_top_products(sales_data, limit=5):
        # Find top-selling products
        pass
    Python

    Solution:

    from typing import Any
    from collections import defaultdict, Counter
    from datetime import datetime
    import csv
    
    SalesRecord = dict[str, Union[str, float, int]]
    MonthlySales = dict[str, list[SalesRecord]]
    MonthlyTotals = dict[str, float]
    
    def load_sales_data(filename: str) -> list[SalesRecord]:
        """Load sales data from CSV file."""
        sales_data: list[SalesRecord] = []
        with open(filename, 'r') as file:
            reader = csv.DictReader(file)
            for row in reader:
                sales_data.append({
                    "date": row["date"],
                    "product": row["product"],
                    "amount": float(row["amount"]),
                    "quantity": int(row["quantity"])
                })
        return sales_data
    
    def group_sales_by_month(sales_data: list[SalesRecord]) -> MonthlySales:
        """Group sales records by month."""
        grouped: defaultdict[str, list[SalesRecord]] = defaultdict(list)
    
        for sale in sales_data:
            # Extract month from date (assuming YYYY-MM-DD format)
            date_str = str(sale["date"])
            month = date_str[:7]  # YYYY-MM
            grouped[month].append(sale)
    
        return dict(grouped)
    
    def calculate_monthly_totals(grouped_sales: MonthlySales) -> MonthlyTotals:
        """Calculate total sales amount for each month."""
        return {
            month: sum(float(sale["amount"]) for sale in sales)
            for month, sales in grouped_sales.items()
        }
    
    def find_top_products(
        sales_data: list[SalesRecord], 
        limit: int = 5
    ) -> list[tuple[str, int]]:
        """Find top-selling products by quantity."""
        product_quantities: Counter[str] = Counter()
    
        for sale in sales_data:
            product = str(sale["product"])
            quantity = int(sale["quantity"])
            product_quantities[product] += quantity
    
        return product_quantities.most_common(limit)
    Python

    5. Advanced Types

    Callable Types

    from typing import Callable, Any
    
    # Function that takes a function as parameter
    def apply_operation(numbers: List[int], operation: Callable[[int], int]) -> List[int]:
        return [operation(n) for n in numbers]
    
    # Function that returns a function
    def create_multiplier(factor: int) -> Callable[[int], int]:
        def multiplier(x: int) -> int:
            return x * factor
        return multiplier
    
    # Complex callable with multiple parameters
    ProcessFunction = Callable[[str, int], bool]
    
    def process_data(data: str, threshold: int, processor: ProcessFunction) -> bool:
        return processor(data, threshold)
    Python

    Literal Types

    from typing import Literal
    
    # Restrict values to specific literals
    def set_log_level(level: Literal["DEBUG", "INFO", "WARNING", "ERROR"]) -> None:
        print(f"Setting log level to {level}")
    
    # Literal with numbers
    def set_port(port: Literal[80, 443, 8080]) -> None:
        print(f"Using port {port}")
    
    # Combined with Union
    Status = Literal["pending", "approved", "rejected"]
    Priority = Literal[1, 2, 3, 4, 5]
    
    def update_task(status: Status, priority: Priority) -> None:
        print(f"Task status: {status}, Priority: {priority}")
    Python

    ๐Ÿ—๏ธ TypedDict – Structured Dictionaries

    from typing import TypedDict, NotRequired, Required, Literal
    from datetime import datetime
    
    # โœ… Basic TypedDict for API responses
    class PersonDict(TypedDict):
        name: str
        age: int
        email: str
    
    # ๐Ÿ†• TypedDict with optional fields (Python 3.11+)
    class UserProfile(TypedDict):
        id: Required[int]
        username: Required[str]
        email: NotRequired[str]
        is_active: NotRequired[bool]
        last_login: NotRequired[datetime]
    
    # ๐ŸŽฏ Real-world API examples
    class APIResponse(TypedDict):
        status: Literal["success", "error"]
        message: str
        data: NotRequired[dict[str, Any]]
        errors: NotRequired[list[str]]
    
    class DatabaseConfig(TypedDict):
        host: str
        port: int
        database: str
        username: str
        password: str
        ssl_enabled: NotRequired[bool]
        connection_timeout: NotRequired[int]
    
    # ๐Ÿ” Inheritance with TypedDict
    class BaseEntity(TypedDict):
        id: int
        created_at: datetime
        updated_at: datetime
    
    class User(BaseEntity):
        username: str
        email: str
        is_active: bool
    
    class Product(BaseEntity):
        name: str
        price: float
        category: str
        in_stock: bool
    
    def create_person(data: PersonDict) -> str:
        """Process person data with type safety."""
        return f"{data['name']} is {data['age']} years old"
    
    def update_user(user_id: int, updates: UserProfile) -> UserProfile:
        """Update user profile with partial data."""
        # In real code, this would update the database
        base_profile: UserProfile = {
            "id": user_id,
            "username": "updated_user"
        }
        # Merge updates (NotRequired fields can be omitted)
        return {**base_profile, **updates}
    
    # ๐Ÿš€ Advanced: Nested TypedDict structures
    class Address(TypedDict):
        street: str
        city: str
        country: str
        postal_code: NotRequired[str]
    
    class CompanyInfo(TypedDict):
        name: str
        address: Address
        employees: list[User]
        founded_year: int
        is_public: NotRequired[bool]
    
    def process_company_data(company: CompanyInfo) -> dict[str, Any]:
        """Process complex nested TypedDict data."""
        return {
            "company_name": company["name"],
            "location": f"{company['address']['city']}, {company['address']['country']}",
            "employee_count": len(company["employees"]),
            "years_in_business": 2025 - company["founded_year"]
        }
    Python

    ๐Ÿ”„ Integration with Pydantic

    from pydantic import BaseModel, ConfigDict
    from typing import Optional
    from datetime import datetime
    
    # โœ… Pydantic models with TypedDict compatibility
    class UserModel(BaseModel):
        model_config = ConfigDict(extra='forbid')
    
        id: int
        username: str
        email: str
        is_active: bool = True
        created_at: datetime = datetime.now()
    
    # ๐Ÿ”„ Convert between TypedDict and Pydantic
    def typeddict_to_pydantic(user_data: UserProfile) -> UserModel:
        """Convert TypedDict to Pydantic model with validation."""
        return UserModel(**user_data)
    
    def pydantic_to_typeddict(user_model: UserModel) -> UserProfile:
        """Convert Pydantic model to TypedDict."""
        return user_model.model_dump()
    
    # ๐ŸŽฏ API endpoint with both TypedDict and Pydantic
    def create_user_endpoint(user_data: UserProfile) -> APIResponse:
        """API endpoint that accepts TypedDict and returns structured response."""
        try:
            # Validate with Pydantic
            user_model = typeddict_to_pydantic(user_data)
    
            # Process the user (save to database, etc.)
            # ... database operations ...
    
            return APIResponse(
                status="success",
                message="User created successfully",
                data={"user_id": user_model.id}
            )
        except Exception as e:
            return APIResponse(
                status="error",
                message="Failed to create user",
                errors=[str(e)]
            )
    Python

    ๐Ÿญ Modern Dataclasses with Advanced Features

    from dataclasses import dataclass, field, InitVar
    from typing import Optional, ClassVar, Self  # Self requires Python 3.11+
    from datetime import datetime
    from enum import Enum
    
    class Priority(Enum):
        LOW = 1
        MEDIUM = 2
        HIGH = 3
        CRITICAL = 4
    
    @dataclass
    class Task:
        title: str
        description: str
        priority: Priority = Priority.MEDIUM
        completed: bool = False
        tags: list[str] = field(default_factory=list)
        created_at: datetime = field(default_factory=datetime.now)
        updated_at: Optional[datetime] = None
    
        # Class variable (shared by all instances)
        total_tasks: ClassVar[int] = 0
    
        def __post_init__(self) -> None:
            """Called after initialization."""
            Task.total_tasks += 1
    
        def mark_completed(self) -> Self:  # Python 3.11+ Self type
            """Mark task as completed and return self for chaining."""
            self.completed = True
            self.updated_at = datetime.now()
            return self
    
        def add_tag(self, tag: str) -> Self:
            """Add a tag and return self for chaining."""
            if tag not in self.tags:
                self.tags.append(tag)
                self.updated_at = datetime.now()
            return self
    
    # ๐Ÿ”„ Dataclass with computed fields
    @dataclass
    class Product:
        name: str
        base_price: float
        tax_rate: float = 0.1
        discount_percent: float = 0.0
    
        @property
        def discounted_price(self) -> float:
            """Calculate price after discount."""
            return self.base_price * (1 - self.discount_percent / 100)
    
        @property
        def final_price(self) -> float:
            """Calculate final price with tax."""
            return self.discounted_price * (1 + self.tax_rate)
    
    # ๐ŸŽฏ Advanced: InitVar for initialization-only parameters
    @dataclass
    class User:
        username: str
        email: str
        password_hash: str = field(repr=False)  # Don't show in repr
        is_active: bool = True
        created_at: datetime = field(default_factory=datetime.now)
    
        # InitVar: only used during initialization, not stored
        raw_password: InitVar[Optional[str]] = None
    
        def __post_init__(self, raw_password: Optional[str]) -> None:
            """Hash password if provided."""
            if raw_password:
                # In real code, use proper password hashing
                self.password_hash = f"hashed_{raw_password}"
    
    # ๐Ÿ—๏ธ Dataclass inheritance
    @dataclass
    class Employee(User):
        employee_id: str
        department: str
        salary: float
        manager: Optional['Employee'] = None  # Forward reference
    
        def get_team_size(self) -> int:
            """Count direct reports (simplified example)."""
            # In real code, this would query a database
            return 0
    
    # ๐Ÿ”ง Frozen dataclasses (immutable)
    @dataclass(frozen=True)
    class Point:
        x: float
        y: float
    
        def distance_from_origin(self) -> float:
            """Calculate distance from origin."""
            return (self.x ** 2 + self.y ** 2) ** 0.5
    
        def translate(self, dx: float, dy: float) -> Self:
            """Return new point translated by dx, dy."""
            return Point(self.x + dx, self.y + dy)
    
    # ๐ŸŽฏ Usage examples
    def demo_dataclasses() -> None:
        # Create task with method chaining
        task = (Task("Learn Python Typing", "Study advanced typing concepts")
                .add_tag("learning")
                .add_tag("python")
                .mark_completed())
    
        # Create user with password
        user = User("alice", "alice@example.com", raw_password="secret123")
    
        # Create product and calculate prices
        product = Product("Laptop", 1000.0, tax_rate=0.08, discount_percent=10)
        print(f"Final price: ${product.final_price:.2f}")
    
        # Immutable point operations
        origin = Point(0, 0)
        moved_point = origin.translate(3, 4)
        distance = moved_point.distance_from_origin()
        print(f"Distance: {distance}")
    Python

    Enum Types

    from enum import Enum, auto
    from typing import Union
    
    class Color(Enum):
        RED = "red"
        GREEN = "green"
        BLUE = "blue"
    
    class Status(Enum):
        PENDING = auto()
        PROCESSING = auto()
        COMPLETED = auto()
        FAILED = auto()
    
    def paint_object(color: Color) -> str:
        return f"Painting with {color.value}"
    
    def check_status(current_status: Status) -> bool:
        return current_status in [Status.COMPLETED, Status.FAILED]
    Python

    6. Generic Types

    Introduction to Generics

    graph TD
        A[Generic Types] --> B[Type Variables]
        A --> C[Generic Classes]
        A --> D[Generic Functions]
        B --> E["T = TypeVar"]
        C --> F["List[T]"]
        C --> G["Dict[K, V]"]
        D --> H[Parameterized Return Types]

    Basic Generics

    from typing import TypeVar, Generic, List
    
    # Type variable
    T = TypeVar('T')
    
    def first_element(items: List[T]) -> T:
        return items[0]
    
    def last_element(items: List[T]) -> T:
        return items[-1]
    
    # Usage preserves type information
    numbers = [1, 2, 3, 4, 5]
    first_num: int = first_element(numbers)  # Type checker knows this is int
    
    strings = ["hello", "world"]
    first_str: str = first_element(strings)  # Type checker knows this is str
    Python

    Generic Classes

    from typing import TypeVar, Generic, Optional
    
    T = TypeVar('T')
    
    class Stack(Generic[T]):
        def __init__(self) -> None:
            self._items: List[T] = []
    
        def push(self, item: T) -> None:
            self._items.append(item)
    
        def pop(self) -> Optional[T]:
            if self._items:
                return self._items.pop()
            return None
    
        def peek(self) -> Optional[T]:
            if self._items:
                return self._items[-1]
            return None
    
        def is_empty(self) -> bool:
            return len(self._items) == 0
    
    # Usage
    int_stack: Stack[int] = Stack()
    int_stack.push(1)
    int_stack.push(2)
    
    str_stack: Stack[str] = Stack()
    str_stack.push("hello")
    str_stack.push("world")
    Python

    Bounded Type Variables

    from typing import TypeVar
    from abc import ABC, abstractmethod
    
    class Comparable(ABC):
        @abstractmethod
        def __lt__(self, other: 'Comparable') -> bool:
            pass
    
    # Bounded type variable
    T = TypeVar('T', bound=Comparable)
    
    def find_min(items: List[T]) -> T:
        if not items:
            raise ValueError("List cannot be empty")
    
        min_item = items[0]
        for item in items[1:]:
            if item < min_item:
                min_item = item
        return min_item
    
    # Constrained type variables
    NumberType = TypeVar('NumberType', int, float)
    
    def add_numbers(a: NumberType, b: NumberType) -> NumberType:
        return a + b
    Python

    Complex Generic Examples

    from typing import TypeVar, Generic, Dict, List, Callable, Optional
    
    K = TypeVar('K')  # Key type
    V = TypeVar('V')  # Value type
    R = TypeVar('R')  # Result type
    
    class Repository(Generic[K, V]):
        def __init__(self) -> None:
            self._data: Dict[K, V] = {}
    
        def save(self, key: K, value: V) -> None:
            self._data[key] = value
    
        def find(self, key: K) -> Optional[V]:
            return self._data.get(key)
    
        def find_all(self) -> List[V]:
            return list(self._data.values())
    
        def transform(self, transformer: Callable[[V], R]) -> List[R]:
            return [transformer(value) for value in self._data.values()]
    
    # Usage
    user_repo: Repository[int, str] = Repository()
    user_repo.save(1, "Alice")
    user_repo.save(2, "Bob")
    
    product_repo: Repository[str, Dict[str, str]] = Repository()
    product_repo.save("laptop", {"name": "Gaming Laptop", "price": "1500"})
    Python

    7. Protocols and Structural Typing

    Understanding Protocols vs Inheritance

    graph TD
        A[๐ŸŽฏ Design Choice] --> B{Relationship Type?}
        B -->|"IS-A"| C[๐Ÿ—๏ธ Inheritance]
        B -->|"CAN-DO"| D[๐Ÿ“‹ Protocol]
    
        C --> E[class Dog extends Animal]
        D --> F[class Dog implements Walkable]
    
        E --> G[โœ… Shared behavior & data]
        F --> H[โœ… Shared interface only]
    
        style C fill:#e3f2fd
        style D fill:#e8f5e8

    ๐Ÿ” When to Use Protocols vs Inheritance

    ScenarioUse InheritanceUse Protocol
    Shared implementationโœ…โŒ
    Multiple inheritance neededโŒโœ…
    Third-party class integrationโŒโœ…
    Duck typing formalizationโŒโœ…
    Interface segregationโŒโœ…

    ๐ŸŽฏ Real-World Protocol Examples

    from typing import Protocol, runtime_checkable, TypeVar, Generic
    from abc import abstractmethod
    import asyncio
    from pathlib import Path
    
    # ๐Ÿ”ง File operations protocol
    class Readable(Protocol):
        def read(self, size: int = -1) -> str: ...
        def readline(self) -> str: ...
        def readlines(self) -> list[str]: ...
    
    class Writable(Protocol):
        def write(self, data: str) -> int: ...
        def writelines(self, lines: list[str]) -> None: ...
        def flush(self) -> None: ...
    
    # โœ… Works with files, StringIO, network streams, etc.
    def copy_content(source: Readable, destination: Writable) -> None:
        """Copy content from any readable source to any writable destination."""
        for line in source.readlines():
            destination.write(line)
        destination.flush()
    
    # ๐ŸŒ HTTP client protocol (works with requests, httpx, aiohttp, etc.)
    @runtime_checkable
    class HTTPClient(Protocol):
        def get(self, url: str, **kwargs) -> 'Response': ...
        def post(self, url: str, data=None, json=None, **kwargs) -> 'Response': ...
    
    class Response(Protocol):
        status_code: int
        text: str
        def json(self) -> dict: ...
    
    def fetch_user_data(client: HTTPClient, user_id: int) -> dict:
        """Fetch user data using any HTTP client implementation."""
        response = client.get(f"https://api.example.com/users/{user_id}")
        if response.status_code == 200:
            return response.json()
        raise RuntimeError(f"Failed to fetch user: {response.status_code}")
    
    # ๐Ÿ’พ Database protocol (works with any database adapter)
    T = TypeVar('T')
    
    class DatabaseConnection(Protocol[T]):
        def execute(self, query: str, params: tuple = ()) -> T: ...
        def fetchone(self) -> dict | None: ...
        def fetchall(self) -> list[dict]: ...
        def commit(self) -> None: ...
        def rollback(self) -> None: ...
    
    def get_user_by_id(conn: DatabaseConnection, user_id: int) -> dict | None:
        """Get user by ID using any database connection."""
        conn.execute("SELECT * FROM users WHERE id = ?", (user_id,))
        return conn.fetchone()
    
    # ๐Ÿ”„ Data processing pipeline protocol
    class DataProcessor(Protocol[T]):
        def process(self, data: T) -> T: ...
        def validate(self, data: T) -> bool: ...
    
    class DataPipeline(Generic[T]):
        def __init__(self) -> None:
            self.processors: list[DataProcessor[T]] = []
    
        def add_processor(self, processor: DataProcessor[T]) -> None:
            self.processors.append(processor)
    
        def process_data(self, data: T) -> T:
            """Process data through all processors."""
            current_data = data
            for processor in self.processors:
                if processor.validate(current_data):
                    current_data = processor.process(current_data)
                else:
                    raise ValueError(f"Validation failed in {processor.__class__.__name__}")
            return current_data
    
    # ๐ŸŽฎ Game entity protocol
    class Drawable(Protocol):
        def draw(self, screen: 'Screen') -> None: ...
    
    class Updatable(Protocol):
        def update(self, delta_time: float) -> None: ...
    
    class Collidable(Protocol):
        def get_bounds(self) -> 'Rectangle': ...
        def on_collision(self, other: 'Collidable') -> None: ...
    
    # Game entity that implements multiple protocols
    class Player:
        def __init__(self, x: float, y: float) -> None:
            self.x = x
            self.y = y
            self.health = 100
    
        def draw(self, screen: 'Screen') -> None:
            screen.draw_sprite("player", self.x, self.y)
    
        def update(self, delta_time: float) -> None:
            # Update player logic
            pass
    
        def get_bounds(self) -> 'Rectangle':
            return Rectangle(self.x, self.y, 32, 32)
    
        def on_collision(self, other: 'Collidable') -> None:
            if isinstance(other, Enemy):
                self.health -= 10
    
    class Enemy:
        def __init__(self, x: float, y: float) -> None:
            self.x = x
            self.y = y
    
        def draw(self, screen: 'Screen') -> None:
            screen.draw_sprite("enemy", self.x, self.y)
    
        def update(self, delta_time: float) -> None:
            # AI logic
            pass
    
        def get_bounds(self) -> 'Rectangle':
            return Rectangle(self.x, self.y, 24, 24)
    
        def on_collision(self, other: 'Collidable') -> None:
            pass
    
    # Game systems work with protocols
    def update_entities(entities: list[Updatable], delta_time: float) -> None:
        """Update all updatable entities."""
        for entity in entities:
            entity.update(delta_time)
    
    def render_entities(entities: list[Drawable], screen: 'Screen') -> None:
        """Render all drawable entities."""
        for entity in entities:
            entity.draw(screen)
    
    def check_collisions(entities: list[Collidable]) -> None:
        """Check collisions between all collidable entities."""
        for i, entity1 in enumerate(entities):
            for entity2 in entities[i+1:]:
                if rectangles_overlap(entity1.get_bounds(), entity2.get_bounds()):
                    entity1.on_collision(entity2)
                    entity2.on_collision(entity1)
    Python

    ๐Ÿš€ Async Protocols

    from typing import Protocol, AsyncIterator, Awaitable
    
    class AsyncReadable(Protocol):
        async def read(self, size: int = -1) -> str: ...
        async def readline(self) -> str: ...
        def __aiter__(self) -> AsyncIterator[str]: ...
    
    class AsyncWritable(Protocol):
        async def write(self, data: str) -> int: ...
        async def flush(self) -> None: ...
    
    async def async_copy_content(source: AsyncReadable, dest: AsyncWritable) -> None:
        """Async copy operation using protocols."""
        async for line in source:
            await dest.write(line)
        await dest.flush()
    
    # WebSocket protocol
    class WebSocketConnection(Protocol):
        async def send(self, message: str) -> None: ...
        async def receive(self) -> str: ...
        async def close(self) -> None: ...
    
    async def chat_handler(websocket: WebSocketConnection) -> None:
        """Handle chat messages via WebSocket."""
        try:
            async for message in websocket:
                # Echo the message back
                await websocket.send(f"Echo: {message}")
        finally:
            await websocket.close()
    Python

    โšก Performance Considerations

    graph TD
        A[Protocol Performance] --> B[โœ… Zero Runtime Cost]
        A --> C[โš ๏ธ Runtime Checkable Cost]
        A --> D[๐ŸŽฏ Design Benefits]
    
        B --> E[Type checking only]
        C --> F["isinstance() overhead"]
        D --> G[Better code organization]
        D --> H[Easier testing]
    
        style B fill:#e8f5e8
        style C fill:#fff3e0
        style D fill:#e3f2fd

    ๐Ÿงช Testing with Protocols

    from typing import Protocol
    import pytest
    from unittest.mock import Mock
    
    class EmailSender(Protocol):
        def send_email(self, to: str, subject: str, body: str) -> bool: ...
    
    class NotificationService:
        def __init__(self, email_sender: EmailSender) -> None:
            self.email_sender = email_sender
    
        def notify_user(self, user_email: str, message: str) -> bool:
            return self.email_sender.send_email(
                to=user_email,
                subject="Notification",
                body=message
            )
    
    # โœ… Easy to test with mocks
    def test_notification_service():
        # Mock implements the protocol automatically
        mock_sender = Mock(spec=EmailSender)
        mock_sender.send_email.return_value = True
    
        service = NotificationService(mock_sender)
        result = service.notify_user("test@example.com", "Hello!")
    
        assert result is True
        mock_sender.send_email.assert_called_once_with(
            to="test@example.com",
            subject="Notification", 
            body="Hello!"
        )
    
    # โœ… Test with fake implementation
    class FakeEmailSender:
        def __init__(self) -> None:
            self.sent_emails: list[dict] = []
    
        def send_email(self, to: str, subject: str, body: str) -> bool:
            self.sent_emails.append({"to": to, "subject": subject, "body": body})
            return True
    
    def test_notification_service_with_fake():
        fake_sender = FakeEmailSender()
        service = NotificationService(fake_sender)
    
        service.notify_user("test@example.com", "Hello!")
    
        assert len(fake_sender.sent_emails) == 1
        assert fake_sender.sent_emails[0]["to"] == "test@example.com"
    Python

    Generic Protocols

    from typing import Protocol, TypeVar, Generic
    
    T = TypeVar('T')
    T_co = TypeVar('T_co', covariant=True)
    
    class Container(Protocol[T_co]):
        def __len__(self) -> int: ...
        def __iter__(self) -> Iterator[T_co]: ...
    
    class Comparable(Protocol):
        def __lt__(self: T, other: T) -> bool: ...
    
    def sort_container(container: Container[Comparable]) -> List[Comparable]:
        return sorted(container)
    
    # Works with any container of comparable items
    numbers = [3, 1, 4, 1, 5]
    sorted_numbers = sort_container(numbers)  # List[int]
    
    words = ["banana", "apple", "cherry"]
    sorted_words = sort_container(words)  # List[str]
    Python

    Complex Protocol Examples

    from typing import Protocol, Any, Dict
    
    class JSONSerializable(Protocol):
        def to_dict(self) -> Dict[str, Any]: ...
        @classmethod
        def from_dict(cls, data: Dict[str, Any]) -> 'JSONSerializable': ...
    
    class DatabaseModel(Protocol):
        id: int
    
        def save(self) -> None: ...
        def delete(self) -> None: ...
        @classmethod
        def find_by_id(cls, model_id: int) -> Optional['DatabaseModel']: ...
    
    class APIResource(Protocol):
        def get(self, resource_id: str) -> Dict[str, Any]: ...
        def post(self, data: Dict[str, Any]) -> Dict[str, Any]: ...
        def put(self, resource_id: str, data: Dict[str, Any]) -> Dict[str, Any]: ...
        def delete(self, resource_id: str) -> bool: ...
    
    def process_api_resource(resource: APIResource, action: str) -> Any:
        if action == "list":
            return resource.get("")
        elif action == "create":
            return resource.post({"name": "test"})
        # ... more actions
    Python

    8. Type Checking Tools

    Static Type Checkers Comparison

    graph TD
        A[Type Checkers] --> B[mypy]
        A --> C[pyright/pylance]
        A --> D[pyre]
        B --> E[Gradual Typing]
        B --> F[Plugin System]
        C --> G[Fast Performance]
        C --> H[VS Code Integration]
        D --> I[Facebook/Meta]
        D --> J[Advanced Features]

    Setting Up mypy

    # mypy.ini
    [mypy]
    python_version = 3.11
    warn_return_any = True
    warn_unused_configs = True
    disallow_untyped_defs = True
    disallow_incomplete_defs = True
    check_untyped_defs = True
    disallow_untyped_decorators = True
    no_implicit_optional = True
    warn_redundant_casts = True
    warn_unused_ignores = True
    warn_no_return = True
    warn_unreachable = True
    strict_equality = True
    
    [mypy-tests.*]
    disallow_untyped_defs = False
    INI

    Type Checking Examples

    # example.py
    from typing import List, Optional, Union
    
    def process_data(data: List[Union[int, str]]) -> Optional[str]:
        if not data:
            return None
    
        result = []
        for item in data:
            if isinstance(item, int):
                result.append(str(item * 2))
            else:
                result.append(item.upper())
    
        return " ".join(result)
    
    # Type checking will catch these errors:
    # process_data(42)  # Error: Argument 1 has incompatible type "int"
    # process_data([1, 2, 3.14])  # Error: List item 2 has incompatible type "float"
    Python

    Type Ignoring and Suppression

    from typing import Any, cast
    
    def legacy_function() -> Any:
        # Legacy code that returns various types
        return {"mixed": "data", "numbers": [1, 2, 3]}
    
    def modern_function() -> Dict[str, Any]:
        result = legacy_function()
        # Type assertion for type checker
        return cast(Dict[str, Any], result)
    
    def problematic_code() -> None:
        # Sometimes you need to ignore type checking
        data = some_untyped_library_function()  # type: ignore
    
        # Or suppress specific error codes
        value = unsafe_operation()  # type: ignore[attr-defined]
    Python

    Advanced Type Checking Configuration

    # Type checking with conditional imports
    from typing import TYPE_CHECKING
    
    if TYPE_CHECKING:
        # Only imported during type checking, not at runtime
        from expensive_module import ExpensiveClass
    
    def process_expensive_data(data: 'ExpensiveClass') -> str:
        # Forward reference avoids runtime import
        return data.process()
    
    # Reveal types for debugging
    def debug_types() -> None:
        x = [1, 2, 3]
        reveal_type(x)  # Revealed type is 'builtins.list[builtins.int]'
    
        y = {1: "one", 2: "two"}
        reveal_type(y)  # Revealed type is 'builtins.dict[builtins.int, builtins.str]'
    Python

    9. Best Practices

    Type Annotation Best Practices

    flowchart TD
        A[Type Annotation Best Practices] --> B[Start Simple]
        A --> C[Be Specific]
        A --> D[Use Type Aliases]
        A --> E[Document Complex Types]
    
        B --> F[Add types gradually]
        C --> G[Prefer specific types over Any]
        D --> H[Make code readable]
        E --> I[Complex unions need explanation]

    Progressive Typing Strategy

    # Phase 1: Start with function signatures
    def calculate_total(items: list, tax_rate: float) -> float:
        subtotal = sum(item.price for item in items)
        return subtotal * (1 + tax_rate)
    
    # Phase 2: Add more specific types
    from typing import List
    
    def calculate_total_v2(items: List[dict], tax_rate: float) -> float:
        subtotal = sum(item["price"] for item in items)
        return subtotal * (1 + tax_rate)
    
    # Phase 3: Use proper data structures
    from dataclasses import dataclass
    from typing import List
    
    @dataclass
    class Item:
        name: str
        price: float
        category: str
    
    def calculate_total_v3(items: List[Item], tax_rate: float) -> float:
        subtotal = sum(item.price for item in items)
        return subtotal * (1 + tax_rate)
    Python

    Error Handling with Types

    from typing import Union, Tuple, Optional
    from dataclasses import dataclass
    
    @dataclass
    class Error:
        message: str
        code: int
    
    # Result type pattern
    Result = Union[Tuple[bool, str], Tuple[bool, Error]]
    
    def validate_email(email: str) -> Result:
        if "@" not in email:
            return False, Error("Invalid email format", 400)
        if len(email) < 5:
            return False, Error("Email too short", 400)
        return True, "Valid email"
    
    # Option type pattern
    def find_user_by_id(user_id: int) -> Optional[dict]:
        users_db = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
        return users_db.get(user_id)
    
    # Exception-based approach with types
    class UserNotFoundError(Exception):
        def __init__(self, user_id: int) -> None:
            super().__init__(f"User with ID {user_id} not found")
            self.user_id = user_id
    
    def get_user_by_id(user_id: int) -> dict:
        users_db = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
        if user_id not in users_db:
            raise UserNotFoundError(user_id)
        return users_db[user_id]
    Python

    Performance Considerations

    from typing import Iterator, Generator, List
    import sys
    
    # Use Iterator/Generator for memory efficiency
    def read_large_file(filename: str) -> Iterator[str]:
        with open(filename, 'r') as file:
            for line in file:
                yield line.strip()
    
    def process_numbers() -> Generator[int, None, None]:
        for i in range(1000000):
            yield i * 2
    
    # Avoid creating unnecessary intermediate lists
    def sum_squares_efficient(numbers: Iterator[int]) -> int:
        return sum(x * x for x in numbers)
    
    def sum_squares_inefficient(numbers: Iterator[int]) -> int:
        # Don't do this - creates intermediate list
        squares: List[int] = [x * x for x in numbers]
        return sum(squares)
    Python

    Type-Safe Configuration

    from typing import TypedDict, Literal, get_type_hints
    from dataclasses import dataclass
    import os
    
    class DatabaseConfig(TypedDict):
        host: str
        port: int
        username: str
        password: str
        database: str
    
    class RedisConfig(TypedDict):
        host: str
        port: int
        password: str
    
    class AppConfig(TypedDict):
        debug: bool
        environment: Literal["development", "staging", "production"]
        database: DatabaseConfig
        redis: RedisConfig
    
    def load_config_from_env() -> AppConfig:
        return {
            "debug": os.getenv("DEBUG", "false").lower() == "true",
            "environment": os.getenv("ENVIRONMENT", "development"),
            "database": {
                "host": os.getenv("DB_HOST", "localhost"),
                "port": int(os.getenv("DB_PORT", "5432")),
                "username": os.getenv("DB_USER", ""),
                "password": os.getenv("DB_PASSWORD", ""),
                "database": os.getenv("DB_NAME", ""),
            },
            "redis": {
                "host": os.getenv("REDIS_HOST", "localhost"),
                "port": int(os.getenv("REDIS_PORT", "6379")),
                "password": os.getenv("REDIS_PASSWORD", ""),
            }
        }
    Python

    10. Real-World Examples

    Web API with Type Safety

    from typing import Dict, List, Optional, Union
    from dataclasses import dataclass, asdict
    from datetime import datetime
    from enum import Enum
    
    class UserRole(Enum):
        ADMIN = "admin"
        USER = "user"
        MODERATOR = "moderator"
    
    @dataclass
    class User:
        id: int
        username: str
        email: str
        role: UserRole
        created_at: datetime
        is_active: bool = True
    
    @dataclass
    class CreateUserRequest:
        username: str
        email: str
        password: str
        role: UserRole = UserRole.USER
    
    @dataclass
    class UserResponse:
        id: int
        username: str
        email: str
        role: str
        created_at: str
        is_active: bool
    
    class UserService:
        def __init__(self) -> None:
            self._users: Dict[int, User] = {}
            self._next_id = 1
    
        def create_user(self, request: CreateUserRequest) -> UserResponse:
            user = User(
                id=self._next_id,
                username=request.username,
                email=request.email,
                role=request.role,
                created_at=datetime.now()
            )
            self._users[user.id] = user
            self._next_id += 1
    
            return UserResponse(
                id=user.id,
                username=user.username,
                email=user.email,
                role=user.role.value,
                created_at=user.created_at.isoformat(),
                is_active=user.is_active
            )
    
        def get_user(self, user_id: int) -> Optional[UserResponse]:
            user = self._users.get(user_id)
            if not user:
                return None
    
            return UserResponse(
                id=user.id,
                username=user.username,
                email=user.email,
                role=user.role.value,
                created_at=user.created_at.isoformat(),
                is_active=user.is_active
            )
    
        def list_users(self, role: Optional[UserRole] = None) -> List[UserResponse]:
            users = self._users.values()
            if role:
                users = [u for u in users if u.role == role]
    
            return [
                UserResponse(
                    id=user.id,
                    username=user.username,
                    email=user.email,
                    role=user.role.value,
                    created_at=user.created_at.isoformat(),
                    is_active=user.is_active
                )
                for user in users
            ]
    Python

    Data Processing Pipeline

    from typing import Protocol, TypeVar, Generic, Callable, Iterator, List
    from abc import abstractmethod
    
    T = TypeVar('T')
    U = TypeVar('U')
    
    class Processor(Protocol[T, U]):
        @abstractmethod
        def process(self, item: T) -> U: ...
    
    class Filter(Protocol[T]):
        @abstractmethod
        def should_include(self, item: T) -> bool: ...
    
    class Pipeline(Generic[T]):
        def __init__(self, data: Iterator[T]) -> None:
            self._data = data
    
        def filter(self, predicate: Filter[T]) -> 'Pipeline[T]':
            filtered_data = (item for item in self._data if predicate.should_include(item))
            return Pipeline(filtered_data)
    
        def map(self, processor: Processor[T, U]) -> 'Pipeline[U]':
            mapped_data = (processor.process(item) for item in self._data)
            return Pipeline(mapped_data)
    
        def collect(self) -> List[T]:
            return list(self._data)
    
    # Concrete implementations
    @dataclass
    class Person:
        name: str
        age: int
        salary: float
    
    class AdultFilter:
        def should_include(self, person: Person) -> bool:
            return person.age >= 18
    
    class SalaryProcessor:
        def __init__(self, tax_rate: float) -> None:
            self.tax_rate = tax_rate
    
        def process(self, person: Person) -> float:
            return person.salary * (1 - self.tax_rate)
    
    # Usage
    people = [
        Person("Alice", 25, 50000),
        Person("Bob", 17, 30000),
        Person("Charlie", 30, 75000)
    ]
    
    pipeline = Pipeline(iter(people))
    net_salaries = (pipeline
        .filter(AdultFilter())
        .map(SalaryProcessor(0.25))
        .collect())
    Python

    Testing with Types

    from typing import Any, Dict, List, Callable, TypeVar
    import unittest
    from unittest.mock import Mock, patch
    
    F = TypeVar('F', bound=Callable[..., Any])
    
    def typed_mock(**kwargs: Any) -> Mock:
        """Create a mock with better type support."""
        return Mock(**kwargs)
    
    class TypedTestCase(unittest.TestCase):
        def assert_type_equal(self, obj: Any, expected_type: type) -> None:
            """Assert that an object is of the expected type."""
            self.assertIsInstance(obj, expected_type)
    
        def assert_callable_with_signature(
            self, 
            func: Callable[..., Any], 
            args: List[Any], 
            kwargs: Dict[str, Any]
        ) -> None:
            """Assert that a function can be called with given arguments."""
            try:
                func(*args, **kwargs)
            except Exception as e:
                self.fail(f"Function call failed: {e}")
    
    class TestUserService(TypedTestCase):
        def setUp(self) -> None:
            self.user_service = UserService()
    
        def test_create_user_returns_correct_type(self) -> None:
            request = CreateUserRequest(
                username="test_user",
                email="test@example.com",
                password="password123"
            )
    
            response = self.user_service.create_user(request)
    
            self.assert_type_equal(response, UserResponse)
            self.assertEqual(response.username, "test_user")
            self.assertEqual(response.email, "test@example.com")
    
        @patch('datetime.datetime')
        def test_create_user_with_mocked_datetime(self, mock_datetime: Mock) -> None:
            mock_now = datetime(2023, 1, 1, 12, 0, 0)
            mock_datetime.now.return_value = mock_now
    
            request = CreateUserRequest(
                username="test_user",
                email="test@example.com",
                password="password123"
            )
    
            response = self.user_service.create_user(request)
    
            self.assertEqual(response.created_at, mock_now.isoformat())
    Python

    11. Common Pitfalls and Troubleshooting

    ๐Ÿšจ Most Common Type Errors

    1. Argument Type Mismatch

    # โŒ Error: Argument 1 has incompatible type "int"; expected "str"
    def greet(name: str) -> str:
        return f"Hello, {name}!"
    
    greet(123)  # Should be greet("123")
    Python

    Solution: Always check argument types match function signatures.

    2. Optional vs Non-Optional Confusion

    # โŒ Error: Item "None" of "Optional[str]" has no attribute "upper"
    def get_user_name(user_id: int) -> Optional[str]:
        users = {1: "Alice", 2: "Bob"}
        return users.get(user_id)
    
    def format_name(user_id: int) -> str:
        name = get_user_name(user_id)
        return name.upper()  # Error! name might be None
    Python

    Solution: Always check for None before using Optional values.

    # โœ… Correct approach
    def format_name(user_id: int) -> str:
        name = get_user_name(user_id)
        if name is None:
            return "Unknown User"
        return name.upper()
    Python

    3. Generic Type Parameter Missing

    # โŒ Missing type parameters for generic type "dict"
    def process_data(data: dict) -> None:  # Too generic!
        pass
    
    # โœ… Specify type parameters
    def process_data(data: dict[str, int]) -> None:
        pass
    Python

    ๐Ÿ”ง Debugging Type Issues

    Using reveal_type()

    def debug_function(items: list[str]) -> None:
        reveal_type(items)  # Reveals: list[str]
    
        processed = [item.upper() for item in items]
        reveal_type(processed)  # Reveals: list[str]
    
        filtered = [item for item in processed if len(item) > 3]
        reveal_type(filtered)  # Reveals: list[str]
    Python

    Common Type Checker Messages

    graph TD
        A[Type Error Categories] --> B[๐Ÿ”ด Incompatible Types]
        A --> C[๐ŸŸก Missing Type Info]
        A --> D[๐ŸŸ  Union Issues]
        A --> E[๐Ÿ”ต Generic Problems]
    
        B --> F["Expected X, got Y"]
        C --> G["has no attribute"]
        D --> H["Too many union members"]
        E --> I["Missing type parameters"]
    
        style B fill:#ffebee
        style C fill:#fff8e1
        style D fill:#fff3e0
        style E fill:#e3f2fd

    ๐Ÿ› ๏ธ IDE Integration and Setup

    VS Code Setup

    1. Install Python Extension
    2. Enable Pylance (Microsoft’s Python language server)
    3. Configure settings.json:
    {
        "python.analysis.typeCheckingMode": "strict",
        "python.analysis.autoImportCompletions": true,
        "python.analysis.diagnosticMode": "workspace"
    }
    JSON

    PyCharm Setup

    1. Enable Type Checking: Settings โ†’ Editor โ†’ Inspections โ†’ Python โ†’ Type checker
    2. Configure External Tool: Add mypy as external tool
    3. Enable Type Hints: Settings โ†’ Editor โ†’ Code Style โ†’ Python

    ๐Ÿš€ Performance and Type Checking

    Type Checking Performance Tips

    # โœ… Use TYPE_CHECKING for expensive imports
    from typing import TYPE_CHECKING
    
    if TYPE_CHECKING:
        from expensive_module import ExpensiveClass
    else:
        ExpensiveClass = object
    
    def process_data(data: 'ExpensiveClass') -> str:
        # Forward reference prevents runtime import cost
        return str(data)
    
    # โœ… Use string annotations for forward references
    class Node:
        def __init__(self, value: int, parent: 'Optional[Node]' = None) -> None:
            self.value = value
            self.parent = parent
            self.children: list['Node'] = []
    Python

    Runtime Performance Considerations

    # โœ… Type hints have minimal runtime overhead
    def typed_function(data: list[int]) -> int:
        return sum(data)
    
    # Type hints are stored in __annotations__ and ignored at runtime
    print(typed_function.__annotations__)
    # Output: {'data': list[int], 'return': int}
    Python

    ๐Ÿ“‹ Type Checking Configuration

    mypy Configuration Examples

    # mypy.ini - Strict configuration
    [mypy]
    python_version = 3.11
    strict = True
    warn_return_any = True
    warn_unused_configs = True
    
    # Per-module configuration
    [mypy-tests.*]
    ignore_errors = True
    
    [mypy-external_lib.*]
    ignore_missing_imports = True
    
    # Gradual adoption
    [mypy-legacy_module.*]
    disallow_untyped_defs = False
    INI

    pyproject.toml Configuration

    [tool.mypy]
    python_version = "3.11"
    strict = true
    exclude = ["tests/", "build/"]
    
    [[tool.mypy.overrides]]
    module = "external_lib.*"
    ignore_missing_imports = true
    INI

    ๐ŸŽฏ Migration Strategies

    Legacy Code Migration

    graph LR
        A[Legacy Code] --> B[1. Add Return Types]
        B --> C[2. Add Parameter Types]
        C --> D[3. Add Variable Types]
        D --> E[4. Enable Strict Mode]
        E --> F[5. Full Coverage]
    
        style A fill:#ffebee
        style F fill:#e8f5e8

    Gradual Typing Example

    # Step 1: Original code
    def process_user_data(user_data):
        return user_data["name"].upper()
    
    # Step 2: Add return type
    def process_user_data(user_data) -> str:
        return user_data["name"].upper()
    
    # Step 3: Add parameter type
    def process_user_data(user_data: dict) -> str:
        return user_data["name"].upper()
    
    # Step 4: Be more specific
    def process_user_data(user_data: dict[str, str]) -> str:
        return user_data["name"].upper()
    
    # Step 5: Use proper types
    from typing import TypedDict
    
    class UserData(TypedDict):
        name: str
        email: str
        age: int
    
    def process_user_data(user_data: UserData) -> str:
        return user_data["name"].upper()
    Python

    ๐Ÿ” Testing Type Annotations

    import pytest
    from typing import get_type_hints
    
    def test_function_type_hints():
        """Test that functions have correct type hints."""
        def sample_function(x: int, y: str) -> bool:
            return len(y) > x
    
        hints = get_type_hints(sample_function)
        assert hints['x'] == int
        assert hints['y'] == str
        assert hints['return'] == bool
    
    def test_class_type_hints():
        """Test class attribute type hints."""
        class Sample:
            name: str
            age: int
    
        hints = get_type_hints(Sample)
        assert hints['name'] == str
        assert hints['age'] == int
    Python

    12. Learning Roadmap

    ๐Ÿ“š Structured Learning Path

    graph TD
        A[๐Ÿš€ Beginner] --> B[๐Ÿ“š Intermediate]
        B --> C[๐ŸŽฏ Advanced]
        C --> D[๐Ÿš€ Expert]
    
        A --> A1[Basic types: str, int, float]
        A --> A2[Optional and Union types]
        A --> A3[List and dict typing]
    
        B --> B1[Generic types and TypeVar]
        B --> B2[Protocols and ABC]
        B --> B3[TypedDict and dataclasses]
    
        C --> C1[Complex generics and bounds]
        C --> C2[Custom protocols]
        C --> C3[Type guards and narrowing]
    
        D --> D1[Variance and contravariance]
        D --> D2[Plugin development]
        D --> D3[Type system design]
    
        style A fill:#e8f5e8
        style B fill:#fff3e0
        style C fill:#ffebee
        style D fill:#e3f2fd

    ๐ŸŽฏ Practice Projects by Skill Level

    Beginner Projects

    1. Calculator with Types: Build a calculator with full type annotations
    2. Contact Book: Create a contact management system
    3. Weather App: Build a weather data processor with APIs

    Intermediate Projects

    1. REST API: Build a typed FastAPI application
    2. Data Analysis Tool: Create a pandas wrapper with proper types
    3. Configuration Manager: Build a type-safe config system

    Advanced Projects

    1. ORM Framework: Design a mini-ORM with generic types
    2. Plugin System: Create an extensible plugin architecture
    3. DSL Creator: Build a domain-specific language with typing

    ๐Ÿ“– Essential Resources

    Official Documentation

    Tools and Libraries

    • Type Checkers: mypy, pyright, pyre
    • Runtime Libraries: pydantic, marshmallow, cattrs
    • IDE Support: VS Code + Pylance, PyCharm

    Community Resources


    Conclusion

    Python typing has evolved from a simple annotation system to a powerful tool for building robust, maintainable applications. Here’s a summary of key takeaways:

    Type Adoption Strategy

    graph LR
        A[Start] --> B[Basic Types]
        B --> C[Collections]
        C --> D[Advanced Features]
        D --> E[Protocols]
        E --> F[Generics]
        F --> G[Expert Level]

    Benefits Realized

    1. Development Speed: Better IDE support and autocomplete
    2. Code Quality: Early detection of type-related bugs
    3. Documentation: Self-documenting code through type hints
    4. Refactoring Safety: Confident code changes with type checking
    5. Team Collaboration: Clearer interfaces and contracts

    Next Steps

    1. Practice: Start adding types to your existing projects gradually
    2. Tools: Set up mypy or pyright in your development workflow
    3. Community: Explore typed libraries and contribute to typing discussions
    4. Advanced Topics: Dive deeper into variance, higher-kinded types, and dependent typing

    Resources for Further Learning

    Remember: Type hints are a tool to make your code better, not a burden. Start small, be consistent, and gradually adopt more advanced features as you become comfortable with the basics.


    Discover more from Altgr Blog

    Subscribe to get the latest posts sent to your email.

    Leave a Reply

    Your email address will not be published. Required fields are marked *