New in 2026: Master Python for AI, Data Science

ProgrammingPythonPython Basic Tutorial

Advanced Python Type Patterns and Performance Optimization (Part 4)

Python developer using type hints and annotations with IDE showing intelligent autocomplete and type checking features

In Parts 1-3, you mastered Python’s type() function, learned type hints and annotations, and discovered when to use duck typing versus static typing. Now it’s time to level up with advanced type patterns that separate junior developers from senior ones.

Previous parts in this series

After building type-safe systems for Fortune 500 companies and open-source projects with millions of users, I’ll show you the professional-grade typing techniques that make Python codebases truly maintainable. You’ll learn generics, protocols, advanced type variables, and performance optimization strategies that most Python developers never master.

Generic Types: Writing Reusable, Type-Safe Code

Generic types let you write functions and classes that work with multiple types while maintaining type safety. Think of them as templates that get filled in with specific types later.

Basic Generic Functions

from typing import TypeVar, List, Optional, Generic, Dict

T = TypeVar('T')
K = TypeVar('K')  # Key type
V = TypeVar('V')  # Value type

def first_or_none(items: List[T]) -> Optional[T]:
    """Get first item or None if list is empty."""
    return items[0] if items else None

def last_or_none(items: List[T]) -> Optional[T]:
    """Get last item or None if list is empty."""
    return items[-1] if items else None

# Usage with type inference
numbers = [1, 2, 3, 4, 5]
first_number = first_or_none(numbers)  # Type: Optional[int]

words = ["hello", "world", "python"]
first_word = first_or_none(words)  # Type: Optional[str]

# Type checker knows the return types!
if first_number is not None:
    print(first_number + 10)  # Safe: knows it's int
    
if first_word is not None:
    print(first_word.upper())  # Safe: knows it's str

Advanced Generic Functions with Multiple Type Variables

from typing import Callable, Tuple

def map_pair(func: Callable[[T], U], pair: Tuple[T, T]) -> Tuple[U, U]:
    """Apply function to both elements of a pair."""
    return (func(pair[0]), func(pair[1]))

def zip_dict(keys: List[K], values: List[V]) -> Dict[K, V]:
    """Create dictionary from key and value lists."""
    return dict(zip(keys, values))

# Examples
number_pair = (10, 20)
string_pair = map_pair(str, number_pair)  # Type: Tuple[str, str]
print(string_pair)  # ('10', '20')

keys = ["a", "b", "c"]
values = [1, 2, 3]
result = zip_dict(keys, values)  # Type: Dict[str, int]
print(result)  # {'a': 1, 'b': 2, 'c': 3}

Generic Classes: Building Type-Safe Data Structures

from typing import Generic, Iterator, Optional
from dataclasses import dataclass

@dataclass
class Node(Generic[T]):
    """Generic node for linked structures."""
    data: T
    next: Optional['Node[T]'] = None

class Stack(Generic[T]):
    """Generic stack implementation."""
    
    def __init__(self) -> None:
        self._items: List[T] = []
    
    def push(self, item: T) -> None:
        """Push item onto stack."""
        self._items.append(item)
    
    def pop(self) -> Optional[T]:
        """Pop item from stack."""
        return self._items.pop() if self._items else None
    
    def peek(self) -> Optional[T]:
        """Look at top item without removing."""
        return self._items[-1] if self._items else None
    
    def is_empty(self) -> bool:
        """Check if stack is empty."""
        return len(self._items) == 0
    
    def __len__(self) -> int:
        """Get stack size."""
        return len(self._items)

# Type-safe usage
int_stack = Stack[int]()
int_stack.push(1)
int_stack.push(2)
int_stack.push(3)

top = int_stack.pop()  # Type: Optional[int]
if top is not None:
    print(f"Popped: {top * 2}")  # Type checker knows it's int

string_stack = Stack[str]()
string_stack.push("hello")
string_stack.push("world")

word = string_stack.pop()  # Type: Optional[str]
if word is not None:
    print(f"Popped: {word.upper()}")  # Type checker knows it's str

Bounded Type Variables

from typing import Protocol
from numbers import Number

class Comparable(Protocol):
    """Protocol for objects that can be compared."""
    def __lt__(self, other: 'Comparable') -> bool: ...
    def __gt__(self, other: 'Comparable') -> bool: ...

# Bounded type variable
ComparableType = TypeVar('ComparableType', bound=Comparable)
NumberType = TypeVar('NumberType', bound=Number)

def find_max(items: List[ComparableType]) -> Optional[ComparableType]:
    """Find maximum item that supports comparison."""
    return max(items) if items else None

def safe_divide(a: NumberType, b: NumberType) -> Optional[NumberType]:
    """Safely divide numbers, return None if division by zero."""
    if b == 0:
        return None
    return a / b

# Usage
numbers = [1, 5, 3, 9, 2]
max_num = find_max(numbers)  # Type: Optional[int]

strings = ["apple", "banana", "cherry"]
max_str = find_max(strings)  # Type: Optional[str]

# Type checker prevents errors
result1 = safe_divide(10.0, 3.0)  # Type: Optional[float]
result2 = safe_divide(20, 4)      # Type: Optional[int]

Protocols: Duck Typing with Type Safety

Protocols define interfaces without requiring inheritance, combining the flexibility of duck typing with the safety of static typing.

Creating Custom Protocols

from typing import Protocol, runtime_checkable
from abc import abstractmethod

@runtime_checkable
class Drawable(Protocol):
    """Protocol for objects that can be drawn."""
    
    def draw(self) -> str:
        """Draw the object and return string representation."""
        ...
    
    def get_area(self) -> float:
        """Calculate and return the area."""
        ...

@runtime_checkable
class Serializable(Protocol):
    """Protocol for objects that can be serialized."""
    
    def serialize(self) -> dict:
        """Convert object to dictionary."""
        ...
    
    @classmethod
    def deserialize(cls, data: dict) -> 'Serializable':
        """Create object from dictionary."""
        ...

# Implementations don't need to inherit from protocols
class Circle:
    def __init__(self, radius: float) -> None:
        self.radius = radius
    
    def draw(self) -> str:
        return f"Circle with radius {self.radius}"
    
    def get_area(self) -> float:
        return 3.14159 * self.radius ** 2
    
    def serialize(self) -> dict:
        return {"type": "circle", "radius": self.radius}
    
    @classmethod
    def deserialize(cls, data: dict) -> 'Circle':
        return cls(data["radius"])

class Rectangle:
    def __init__(self, width: float, height: float) -> None:
        self.width = width
        self.height = height
    
    def draw(self) -> str:
        return f"Rectangle {self.width}x{self.height}"
    
    def get_area(self) -> float:
        return self.width * self.height
    
    def serialize(self) -> dict:
        return {"type": "rectangle", "width": self.width, "height": self.height}
    
    @classmethod
    def deserialize(cls, data: dict) -> 'Rectangle':
        return cls(data["width"], data["height"])

# Functions that work with protocols
def render_drawable(obj: Drawable) -> str:
    """Render any drawable object."""
    area = obj.get_area()
    drawing = obj.draw()
    return f"{drawing} (Area: {area:.2f})"

def save_serializable(obj: Serializable, filename: str) -> None:
    """Save any serializable object to file."""
    import json
    data = obj.serialize()
    with open(filename, 'w') as f:
        json.dump(data, f)

# Usage - type checker verifies protocol compliance
shapes: List[Drawable] = [
    Circle(5.0),
    Rectangle(4.0, 6.0)
]

for shape in shapes:
    print(render_drawable(shape))

# Runtime protocol checking
circle = Circle(3.0)
print(isinstance(circle, Drawable))     # True
print(isinstance(circle, Serializable)) # True

Protocol Composition and Inheritance

class Movable(Protocol):
    """Protocol for objects that can be moved."""
    def move(self, dx: float, dy: float) -> None: ...

class Rotatable(Protocol):
    """Protocol for objects that can be rotated."""
    def rotate(self, angle: float) -> None: ...

class Transform(Protocol):
    """Protocol that combines multiple transformation abilities."""
    def move(self, dx: float, dy: float) -> None: ...
    def rotate(self, angle: float) -> None: ...
    def scale(self, factor: float) -> None: ...

class GameSprite:
    """Game sprite that implements multiple protocols."""
    
    def __init__(self, x: float, y: float, angle: float = 0.0) -> None:
        self.x = x
        self.y = y
        self.angle = angle
        self.scale_factor = 1.0
    
    def move(self, dx: float, dy: float) -> None:
        self.x += dx
        self.y += dy
    
    def rotate(self, angle: float) -> None:
        self.angle += angle
    
    def scale(self, factor: float) -> None:
        self.scale_factor *= factor
    
    def draw(self) -> str:
        return f"Sprite at ({self.x}, {self.y}), angle={self.angle}°, scale={self.scale_factor}"

def animate_movable(obj: Movable, path: List[Tuple[float, float]]) -> None:
    """Animate any movable object along a path."""
    for dx, dy in path:
        obj.move(dx, dy)

def apply_transform(obj: Transform, dx: float, dy: float, angle: float, scale: float) -> None:
    """Apply complete transformation to object."""
    obj.move(dx, dy)
    obj.rotate(angle)
    obj.scale(scale)

# Usage
sprite = GameSprite(0, 0)
path = [(1, 0), (0, 1), (-1, 0), (0, -1)]
animate_movable(sprite, path)  # Works because GameSprite implements Movable

apply_transform(sprite, 10, 10, 45, 1.5)  # Works because GameSprite implements Transform
print(sprite.draw())

Advanced Type Variables and Constraints

Covariant and Contravariant Type Variables

from typing import TypeVar, List, Callable, Generic

# Covariant type variable (preserves subtype relationship)
T_co = TypeVar('T_co', covariant=True)

# Contravariant type variable (reverses subtype relationship)
T_contra = TypeVar('T_contra', contravariant=True)

class Producer(Generic[T_co]):
    """Producer that only outputs values (covariant)."""
    
    def __init__(self, value: T_co) -> None:
        self._value = value
    
    def get(self) -> T_co:
        return self._value

class Consumer(Generic[T_contra]):
    """Consumer that only accepts values (contravariant)."""
    
    def consume(self, value: T_contra) -> None:
        print(f"Consuming: {value}")

# Covariance example
class Animal:
    def speak(self) -> str:
        return "Some sound"

class Dog(Animal):
    def speak(self) -> str:
        return "Woof!"

# Producer[Dog] is a subtype of Producer[Animal] (covariant)
dog_producer: Producer[Dog] = Producer(Dog())
animal_producer: Producer[Animal] = dog_producer  # This works!

# Contravariance example
animal_consumer: Consumer[Animal] = Consumer()
dog_consumer: Consumer[Dog] = animal_consumer  # This works!

# Consumer[Animal] can consume Dog instances
dog_consumer.consume(Dog())

Complex Type Constraints

from typing import Union, overload, Literal

# Constrained type variables
AnyStr = TypeVar('AnyStr', str, bytes)
NumericType = TypeVar('NumericType', int, float, complex)

def concat(a: AnyStr, b: AnyStr) -> AnyStr:
    """Concatenate two items of the same string-like type."""
    return a + b

def multiply_numeric(a: NumericType, b: NumericType) -> NumericType:
    """Multiply two numbers of the same numeric type."""
    return a * b

# Usage
str_result = concat("hello", " world")    # Type: str
bytes_result = concat(b"hello", b" world") # Type: bytes

int_result = multiply_numeric(5, 3)        # Type: int
float_result = multiply_numeric(2.5, 4.0)  # Type: float

# Using Literal types for precise control
def get_config(env: Literal["dev", "test", "prod"]) -> dict:
    """Get configuration for specific environment."""
    configs = {
        "dev": {"debug": True, "db": "dev.db"},
        "test": {"debug": False, "db": "test.db"},
        "prod": {"debug": False, "db": "prod.db"}
    }
    return configs[env]

# Type checker ensures only valid environments
dev_config = get_config("dev")    # ✓ Valid
prod_config = get_config("prod")  # ✓ Valid
# invalid_config = get_config("staging")  # ✗ Type error

Type Performance Analysis and Optimization

Measuring Type Checking Performance

import time
import timeit
from typing import List, Dict, Any, Union, Optional

# Benchmark different typing approaches
def benchmark_typing_performance():
    """Compare performance of different type checking strategies."""
    
    # Setup data
    large_list = list(range(100000))
    mixed_data = [1, "hello", 3.14, [1, 2, 3], {"key": "value"}] * 20000
    
    # No type checking
    def process_no_types(data):
        return [str(item) for item in data]
    
    # Runtime type checking
    def process_with_runtime_checks(data: List[Any]) -> List[str]:
        result = []
        for item in data:
            if isinstance(item, (int, float, str)):
                result.append(str(item))
            else:
                result.append(repr(item))
        return result
    
    # Static type hints only (no runtime overhead)
    def process_with_static_types(data: List[Union[int, str, float]]) -> List[str]:
        return [str(item) for item in data]
    
    # Benchmarks
    times = {}
    
    # No types
    times['no_types'] = timeit.timeit(
        lambda: process_no_types(large_list),
        number=10
    )
    
    # Runtime checking
    times['runtime_checks'] = timeit.timeit(
        lambda: process_with_runtime_checks(mixed_data),
        number=10
    )
    
    # Static types (no runtime cost)
    times['static_types'] = timeit.timeit(
        lambda: process_with_static_types(large_list),
        number=10
    )
    
    return times

# Memory usage analysis
def analyze_type_memory_usage():
    """Analyze memory overhead of different typing approaches."""
    import sys
    
    # Function without types
    def plain_function(x, y, z):
        return x + y + z
    
    # Function with simple types
    def typed_function(x: int, y: int, z: int) -> int:
        return x + y + z
    
    # Function with complex types
    def complex_typed_function(
        data: Dict[str, List[Union[int, str, float]]],
        config: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Union[int, float]]:
        # Implementation here
        return {}
    
    print("Memory usage comparison:")
    print(f"Plain function: {sys.getsizeof(plain_function)} bytes")
    print(f"Typed function: {sys.getsizeof(typed_function)} bytes")
    print(f"Complex typed function: {sys.getsizeof(complex_typed_function)} bytes")
    
    print("\nAnnotations storage:")
    print(f"Plain: {getattr(plain_function, '__annotations__', 'None')}")
    print(f"Typed: {typed_function.__annotations__}")
    print(f"Complex: {complex_typed_function.__annotations__}")

# Run benchmarks
if __name__ == "__main__":
    performance_results = benchmark_typing_performance()
    for approach, time_taken in performance_results.items():
        print(f"{approach}: {time_taken:.4f} seconds")
    
    print("\n" + "="*50)
    analyze_type_memory_usage()

Optimizing Type-Heavy Code

from typing import Final, ClassVar, NewType
from dataclasses import dataclass
from functools import lru_cache

# Use Final for constants to help type checkers optimize
MAX_ITEMS: Final[int] = 1000
DEFAULT_TIMEOUT: Final[float] = 30.0
API_VERSION: Final[str] = "v1"

# Use NewType for domain-specific types
UserId = NewType('UserId', int)
Email = NewType('Email', str)
Timestamp = NewType('Timestamp', float)

@dataclass(frozen=True)  # Immutable for better performance
class User:
    """Optimized user data structure."""
    id: UserId
    email: Email
    created_at: Timestamp
    is_active: bool = True
    
    # Class variables for shared data
    _cache: ClassVar[Dict[UserId, 'User']] = {}
    
    @classmethod
    @lru_cache(maxsize=128)
    def get_by_id(cls, user_id: UserId) -> Optional['User']:
        """Cached user lookup with type safety."""
        return cls._cache.get(user_id)
    
    def __post_init__(self) -> None:
        """Add to cache after creation."""
        User._cache[self.id] = self

# Efficient generic container with minimal overhead
class FastGenericContainer(Generic[T]):
    """Memory-efficient generic container."""
    
    __slots__ = ['_data', '_size']
    
    def __init__(self) -> None:
        self._data: List[T] = []
        self._size = 0
    
    def add(self, item: T) -> None:
        """Add item with O(1) amortized complexity."""
        self._data.append(item)
        self._size += 1
    
    def get(self, index: int) -> T:
        """Get item by index."""
        if 0 <= index < self._size:
            return self._data[index]
        raise IndexError("Index out of range")
    
    def __len__(self) -> int:
        return self._size

# Performance-optimized type checking
def fast_type_dispatch(value: Union[int, str, float, list]) -> str:
    """Fast type-based dispatch using isinstance tuple."""
    # Single isinstance call with tuple is faster than multiple calls
    if isinstance(value, (int, float)):
        return f"Number: {value}"
    elif isinstance(value, str):
        return f"String: {value}"
    elif isinstance(value, list):
        return f"List with {len(value)} items"
    else:
        return "Unknown type"

# Example usage
user = User(
    id=UserId(123),
    email=Email("[email protected]"),
    created_at=Timestamp(time.time())
)

container = FastGenericContainer[int]()
for i in range(1000):
    container.add(i)

print(f"Container size: {len(container)}")
print(f"First item: {container.get(0)}")
print(fast_type_dispatch(42))
print(fast_type_dispatch("hello"))

Building Type-Safe Frameworks and Libraries

Plugin Architecture with Type Safety

from typing import Type, TypedDict, get_type_hints
from abc import ABC, abstractmethod
import inspect

class PluginConfig(TypedDict):
    """Typed configuration for plugins."""
    name: str
    version: str
    enabled: bool

class BasePlugin(ABC, Generic[T]):
    """Base class for all plugins with generic data type."""
    
    config: PluginConfig
    
    def __init__(self, config: PluginConfig) -> None:
        self.config = config
    
    @abstractmethod
    def process(self, data: T) -> T:
        """Process data of type T."""
        pass
    
    @abstractmethod
    def validate_input(self, data: Any) -> bool:
        """Validate if data can be processed by this plugin."""
        pass

class PluginRegistry:
    """Type-safe plugin registry."""
    
    def __init__(self) -> None:
        self._plugins: Dict[str, Type[BasePlugin]] = {}
    
    def register(self, plugin_class: Type[BasePlugin]) -> None:
        """Register a plugin class with type validation."""
        # Validate plugin implements required interface
        if not issubclass(plugin_class, BasePlugin):
            raise TypeError(f"{plugin_class} must inherit from BasePlugin")
        
        # Extract type information
        type_hints = get_type_hints(plugin_class.process)
        if 'data' not in type_hints:
            raise TypeError(f"{plugin_class} must have typed 'data' parameter")
        
        plugin_name = getattr(plugin_class, 'PLUGIN_NAME', plugin_class.__name__)
        self._plugins[plugin_name] = plugin_class
    
    def create_plugin(self, name: str, config: PluginConfig) -> BasePlugin:
        """Create plugin instance with type safety."""
        if name not in self._plugins:
            raise KeyError(f"Plugin '{name}' not registered")
        
        plugin_class = self._plugins[name]
        return plugin_class(config)
    
    def list_plugins(self) -> List[str]:
        """List all registered plugin names."""
        return list(self._plugins.keys())

# Example plugin implementations
class TextProcessorPlugin(BasePlugin[str]):
    """Plugin for processing text data."""
    
    PLUGIN_NAME = "text_processor"
    
    def process(self, data: str) -> str:
        """Convert text to uppercase."""
        return data.upper()
    
    def validate_input(self, data: Any) -> bool:
        """Check if data is a string."""
        return isinstance(data, str)

class NumberProcessorPlugin(BasePlugin[Union[int, float]]):
    """Plugin for processing numeric data."""
    
    PLUGIN_NAME = "number_processor"
    
    def process(self, data: Union[int, float]) -> Union[int, float]:
        """Square the number."""
        return data ** 2
    
    def validate_input(self, data: Any) -> bool:
        """Check if data is numeric."""
        return isinstance(data, (int, float))

# Usage example
registry = PluginRegistry()
registry.register(TextProcessorPlugin)
registry.register(NumberProcessorPlugin)

# Type-safe plugin creation and usage
text_plugin = registry.create_plugin("text_processor", {
    "name": "Text Processor",
    "version": "1.0.0",
    "enabled": True
})

result = text_plugin.process("hello world")  # Type: str
print(f"Processed text: {result}")

number_plugin = registry.create_plugin("number_processor", {
    "name": "Number Processor", 
    "version": "1.0.0",
    "enabled": True
})

result = number_plugin.process(5.0)  # Type: Union[int, float]
print(f"Processed number: {result}")

Type-Safe Configuration System

from typing import TypedDict, Union, get_origin, get_args
from dataclasses import dataclass, field
import json

class DatabaseConfig(TypedDict):
    host: str
    port: int
    username: str
    password: str
    database: str

class RedisConfig(TypedDict):
    host: str
    port: int
    password: Optional[str]

class AppConfig(TypedDict):
    debug: bool
    secret_key: str
    database: DatabaseConfig
    redis: RedisConfig
    allowed_hosts: List[str]

@dataclass
class ConfigValidator:
    """Type-safe configuration validator."""
    
    def validate_config(self, config_data: dict, expected_type: Type) -> bool:
        """Validate configuration against TypedDict."""
        if not hasattr(expected_type, '__annotations__'):
            return False
        
        required_fields = expected_type.__annotations__
        
        for field_name, field_type in required_fields.items():
            if field_name not in config_data:
                print(f"Missing required field: {field_name}")
                return False
            
            value = config_data[field_name]
            if not self._check_type(value, field_type):
                print(f"Invalid type for {field_name}: expected {field_type}, got {type(value)}")
                return False
        
        return True
    
    def _check_type(self, value: Any, expected_type: Type) -> bool:
        """Check if value matches expected type."""
        # Handle Optional types
        if get_origin(expected_type) is Union:
            args = get_args(expected_type)
            return any(isinstance(value, arg) for arg in args if arg is not type(None))
        
        # Handle List types
        if get_origin(expected_type) is list:
            if not isinstance(value, list):
                return False
            item_type = get_args(expected_type)[0]
            return all(isinstance(item, item_type) for item in value)
        
        # Handle TypedDict
        if hasattr(expected_type, '__annotations__'):
            return isinstance(value, dict) and self.validate_config(value, expected_type)
        
        # Basic type checking
        return isinstance(value, expected_type)

class ConfigManager:
    """Type-safe configuration manager."""
    
    def __init__(self) -> None:
        self.validator = ConfigValidator()
        self._config: Optional[AppConfig] = None
    
    def load_config(self, config_path: str) -> AppConfig:
        """Load and validate configuration from file."""
        with open(config_path, 'r') as f:
            raw_config = json.load(f)
        
        if not self.validator.validate_config(raw_config, AppConfig):
            raise ValueError("Invalid configuration")
        
        self._config = raw_config
        return self._config
    
    def get_database_config(self) -> DatabaseConfig:
        """Get database configuration."""
        if self._config is None:
            raise RuntimeError("Configuration not loaded")
        return self._config['database']
    
    def get_redis_config(self) -> RedisConfig:
        """Get Redis configuration."""
        if self._config is None:
            raise RuntimeError("Configuration not loaded")
        return self._config['redis']

# Example usage
sample_config = {
    "debug": True,
    "secret_key": "your-secret-key",
    "database": {
        "host": "localhost",
        "port": 5432,
        "username": "admin",
        "password": "password",
        "database": "myapp"
    },
    "redis": {
        "host": "localhost",
        "port": 6379,
        "password": None
    },
    "allowed_hosts": ["localhost", "127.0.0.1"]
}

# Save sample config to file for testing
with open("config.json", "w") as f:
    json.dump(sample_config, f, indent=2)

# Load and validate configuration
config_manager = ConfigManager()
config = config_manager.load_config("config.json")

# Type-safe access to configuration
db_config = config_manager.get_database_config()
print(f"Database host: {db_config['host']}:{db_config['port']}")

redis_config = config_manager.get_redis_config()
print(f"Redis host: {redis_config['host']}:{redis_config['port']}")

Integration with Static Analysis Tools

Advanced mypy Configuration

# mypy.ini - Production-ready configuration
[mypy]
python_version = 3.9

# Import discovery
namespace_packages = True
explicit_package_bases = True

# Type checking strictness
strict = True
warn_return_any = True
warn_unused_configs = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = True
warn_unreachable = True

# Error reporting
show_error_codes = True
show_column_numbers = True
pretty = True
color_output = True

# Per-module settings
[mypy-tests.*]
ignore_errors = True

[mypy-external_lib.*]
ignore_missing_imports = True

# Custom plugin configuration
[mypy.plugins.dataclasses]
init = True
eq = True
order = True

Custom Type Checker Plugins

# custom_plugin.py - Custom mypy plugin
from typing import Type as TypingType, Callable, Optional
from mypy.plugin import Plugin, AttributeContext, FunctionContext
from mypy.nodes import ARG_POS, ARG_STAR, Argument, Var, Decorator
from mypy.types import Type

class CustomValidationPlugin(Plugin):
    """Custom plugin for enhanced type validation."""
    
    def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext], Type]]:
        """Hook for function call type checking."""
        if fullname == 'myapp.validators.validate_email':
            return self._validate_email_hook
        return None
    
    def get_attribute_hook(self, fullname: str) -> Optional[Callable[[AttributeContext], Type]]:
        """Hook for attribute access type checking."""
        if fullname.endswith('.safe_get'):
            return self._safe_get_hook
        return None
    
    def _validate_email_hook(self, context: FunctionContext) -> Type:
        """Custom validation for email validation function."""
        # Add custom type checking logic here
        return context.default_return_type
    
    def _safe_get_hook(self, context: AttributeContext) -> Type:
        """Custom validation for safe attribute access."""
        # Add custom type checking logic here
        return context.default_attr_type

def plugin(version: str) -> type[Plugin]:
    return CustomValidationPlugin

# Usage in code with plugin
from typing import TypeGuard

def is_email(value: str) -> TypeGuard[str]:
    """Type guard for email validation."""
    import re
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, value))

def validate_email(email: str) -> str:
    """Validate email format."""
    if is_email(email):
        return email  # Type checker knows this is a valid email
    raise ValueError("Invalid email format")

# Advanced type guards
def is_list_of_strings(value: Any) -> TypeGuard[List[str]]:
    """Type guard for list of strings."""
    return (isinstance(value, list) and 
            all(isinstance(item, str) for item in value))

def process_strings(data: Any) -> List[str]:
    """Process data if it's a list of strings."""
    if is_list_of_strings(data):
        # Type checker knows data is List[str] here
        return [s.upper() for s in data]
    raise TypeError("Expected list of strings")

Production-Ready Type Patterns

Error Handling with Result Types

from typing import Union, Generic, TypeVar, Optional
from dataclasses import dataclass

T = TypeVar('T')
E = TypeVar('E')

@dataclass(frozen=True)
class Ok(Generic[T]):
    """Success result with value."""
    value: T

@dataclass(frozen=True)
class Err(Generic[E]):
    """Error result with error value."""
    error: E

Result = Union[Ok[T], Err[E]]

class ResultType:
    """Utility methods for Result type."""
    
    @staticmethod
    def ok(value: T) -> Ok[T]:
        """Create success result."""
        return Ok(value)
    
    @staticmethod
    def err(error: E) -> Err[E]:
        """Create error result."""
        return Err(error)
    
    @staticmethod
    def is_ok(result: Result[T, E]) -> bool:
        """Check if result is Ok."""
        return isinstance(result, Ok)
    
    @staticmethod
    def is_err(result: Result[T, E]) -> bool:
        """Check if result is Err."""
        return isinstance(result, Err)
    
    @staticmethod
    def unwrap(result: Result[T, E]) -> T:
        """Get value from Ok result, raise if Err."""
        if isinstance(result, Ok):
            return result.value
        raise RuntimeError(f"Called unwrap on Err: {result.error}")
    
    @staticmethod
    def unwrap_or(result: Result[T, E], default: T) -> T:
        """Get value from Ok result, return default if Err."""
        if isinstance(result, Ok):
            return result.value
        return default

# Example usage with type-safe error handling
def divide_safe(a: float, b: float) -> Result[float, str]:
    """Safe division with Result type."""
    if b == 0:
        return ResultType.err("Division by zero")
    return ResultType.ok(a / b)

def parse_int_safe(value: str) -> Result[int, str]:
    """Safe integer parsing."""
    try:
        return ResultType.ok(int(value))
    except ValueError as e:
        return ResultType.err(f"Invalid integer: {e}")

# Chain operations safely
def calculate_average(numbers_str: List[str]) -> Result[float, str]:
    """Calculate average of string numbers."""
    total = 0
    count = 0
    
    for num_str in numbers_str:
        result = parse_int_safe(num_str)
        if ResultType.is_err(result):
            return result  # Propagate error
        
        total += ResultType.unwrap(result)
        count += 1
    
    if count == 0:
        return ResultType.err("Empty list")
    
    avg_result = divide_safe(total, count)
    return avg_result

# Usage
numbers = ["1", "2", "3", "4", "5"]
result = calculate_average(numbers)

if ResultType.is_ok(result):
    print(f"Average: {ResultType.unwrap(result)}")
else:
    print(f"Error: {result.error}")

State Machine with Type Safety

from enum import Enum
from typing import Dict, Type, Any
from abc import ABC, abstractmethod

class OrderStatus(Enum):
    """Order status enumeration."""
    PENDING = "pending"
    CONFIRMED = "confirmed"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

class OrderState(ABC):
    """Abstract base state for order state machine."""
    
    @abstractmethod
    def confirm(self) -> 'OrderState':
        """Confirm the order."""
        pass
    
    @abstractmethod
    def ship(self) -> 'OrderState':
        """Ship the order."""
        pass
    
    @abstractmethod
    def deliver(self) -> 'OrderState':
        """Deliver the order."""
        pass
    
    @abstractmethod
    def cancel(self) -> 'OrderState':
        """Cancel the order."""
        pass
    
    @property
    @abstractmethod
    def status(self) -> OrderStatus:
        """Get current status."""
        pass

class PendingState(OrderState):
    """Order is pending confirmation."""
    
    def confirm(self) -> 'ConfirmedState':
        return ConfirmedState()
    
    def ship(self) -> 'OrderState':
        raise ValueError("Cannot ship pending order")
    
    def deliver(self) -> 'OrderState':
        raise ValueError("Cannot deliver pending order")
    
    def cancel(self) -> 'CancelledState':
        return CancelledState()
    
    @property
    def status(self) -> OrderStatus:
        return OrderStatus.PENDING

class ConfirmedState(OrderState):
    """Order is confirmed and ready to ship."""
    
    def confirm(self) -> 'ConfirmedState':
        return self  # Already confirmed
    
    def ship(self) -> 'ShippedState':
        return ShippedState()
    
    def deliver(self) -> 'OrderState':
        raise ValueError("Cannot deliver unshipped order")
    
    def cancel(self) -> 'CancelledState':
        return CancelledState()
    
    @property
    def status(self) -> OrderStatus:
        return OrderStatus.CONFIRMED

class ShippedState(OrderState):
    """Order is shipped."""
    
    def confirm(self) -> 'ShippedState':
        return self
    
    def ship(self) -> 'ShippedState':
        return self  # Already shipped
    
    def deliver(self) -> 'DeliveredState':
        return DeliveredState()
    
    def cancel(self) -> 'OrderState':
        raise ValueError("Cannot cancel shipped order")
    
    @property
    def status(self) -> OrderStatus:
        return OrderStatus.SHIPPED

class DeliveredState(OrderState):
    """Order is delivered (final state)."""
    
    def confirm(self) -> 'DeliveredState':
        return self
    
    def ship(self) -> 'DeliveredState':
        return self
    
    def deliver(self) -> 'DeliveredState':
        return self  # Already delivered
    
    def cancel(self) -> 'OrderState':
        raise ValueError("Cannot cancel delivered order")
    
    @property
    def status(self) -> OrderStatus:
        return OrderStatus.DELIVERED

class CancelledState(OrderState):
    """Order is cancelled (final state)."""
    
    def confirm(self) -> 'OrderState':
        raise ValueError("Cannot confirm cancelled order")
    
    def ship(self) -> 'OrderState':
        raise ValueError("Cannot ship cancelled order")
    
    def deliver(self) -> 'OrderState':
        raise ValueError("Cannot deliver cancelled order")
    
    def cancel(self) -> 'CancelledState':
        return self  # Already cancelled
    
    @property
    def status(self) -> OrderStatus:
        return OrderStatus.CANCELLED

@dataclass
class Order:
    """Type-safe order with state machine."""
    
    id: int
    _state: OrderState = field(default_factory=PendingState)
    
    @property
    def status(self) -> OrderStatus:
        """Get current order status."""
        return self._state.status
    
    def confirm(self) -> None:
        """Confirm the order."""
        self._state = self._state.confirm()
    
    def ship(self) -> None:
        """Ship the order."""
        self._state = self._state.ship()
    
    def deliver(self) -> None:
        """Deliver the order."""
        self._state = self._state.deliver()
    
    def cancel(self) -> None:
        """Cancel the order."""
        self._state = self._state.cancel()

# Usage example
order = Order(id=12345)
print(f"Initial status: {order.status}")  # PENDING

order.confirm()
print(f"After confirm: {order.status}")  # CONFIRMED

order.ship()
print(f"After ship: {order.status}")  # SHIPPED

order.deliver()
print(f"After deliver: {order.status}")  # DELIVERED

# This would raise an error:
# order.cancel()  # ValueError: Cannot cancel delivered order

Key Takeaways and Best Practices

  • Use generics to write reusable, type-safe code that works with multiple types
  • Leverage protocols to combine duck typing flexibility with static type safety
  • Implement proper error handling with Result types for robust applications
  • Optimize performance by understanding the zero-runtime cost of static typing
  • Build type-safe frameworks using advanced type patterns and custom validation
  • Use static analysis tools like mypy with proper configuration for maximum benefit

Series Conclusion

You’ve now mastered Python’s type system from basic type() functions to advanced generic types and protocols. You understand when to use duck typing versus static typing, and you can build production-ready type-safe systems that scale.

The techniques in this series will make your code more maintainable, help you catch bugs early, and enable better collaboration with team members. Most importantly, you now have the knowledge to make informed decisions about when and how to apply different typing strategies in your Python projects.

What’s your next typing challenge? Share your experiences applying these advanced patterns in your projects – I’d love to hear how you’re using generics, protocols, and type-safe architecture in real-world applications!

External Links

Tags: Python, Advanced Typing, Generics, Protocols, Type Variables, Performance Optimisation, Static Analysis, mypy, Type Safety, Software Architecture, Python Programming

Related posts
Python

Pydantic Agent Basics: A Complete 2026 Tutorial

ProgrammingPython

Production-Ready MCP Servers — Security, Testing & Deployment

ProgrammingPython

Build Your First MCP Server with Python SDK — Fundamentals

ProgrammingPython

Connect FastAPI to MCP — Two Integration Patterns

Leave a Reply