Advanced Annotation Techniques
Custom Type Annotations
Creating Complex Type Hints
from typing import Union, List, Dict, Callable
def process_data(
data: Union[List[int], List[str]],
transformer: Callable[[str], int] = int
) -> Dict[str, int]:
return {str(item): transformer(item) for item in data}
Annotation Validation
Runtime Type Checking
def validate_annotations(func):
def wrapper(*args, **kwargs):
signature = inspect.signature(func)
bound_arguments = signature.bind(*args, **kwargs)
for name, value in bound_arguments.arguments.items():
annotation = signature.parameters[name].annotation
if annotation is not inspect.Parameter.empty:
if not isinstance(value, annotation):
raise TypeError(f"{name} must be {annotation}")
return func(*args, **kwargs)
return wrapper
@validate_annotations
def create_user(name: str, age: int) -> dict:
return {"name": name, "age": age}
Advanced Annotation Strategies
Technique |
Description |
Use Case |
Generics |
Parameterized Types |
Complex Collections |
Protocols |
Structural Typing |
Duck Typing Validation |
TypedDict |
Precise Dictionary Types |
Structured Data |
from typing import TypeVar, Generic
T = TypeVar('T')
class Repository(Generic[T]):
def __init__(self, items: List[T]):
self._items = items
def filter(self, predicate: Callable[[T], bool]) -> List[T]:
return [item for item in self._items if predicate(item)]
Annotation Workflow
graph TD
A[Function Definition] --> B[Add Complex Annotations]
B --> C{Annotation Processing}
C --> |Type Checking| D[Validate Input/Output]
C --> |Metaprogramming| E[Generate Dynamic Behavior]
C --> |Documentation| F[Generate Metadata]
Forward References
from __future__ import annotations
from typing import List
class TreeNode:
def __init__(self, value, children: List[TreeNode] = None):
self.value = value
self.children = children or []
LabEx Pro Tip
When implementing advanced annotations, LabEx recommends using typing.Protocol
for creating flexible, structurally typed interfaces.
Decorator-Based Annotation Processing
def type_checked(func):
def wrapper(*args, **kwargs):
annotations = func.__annotations__
## Check parameter types
for param, value in zip(func.__code__.co_varnames, args):
if param in annotations:
expected_type = annotations[param]
if not isinstance(value, expected_type):
raise TypeError(f"{param} must be {expected_type}")
result = func(*args, **kwargs)
## Check return type
if 'return' in annotations:
return_type = annotations['return']
if not isinstance(result, return_type):
raise TypeError(f"Return value must be {return_type}")
return result
return wrapper
@type_checked
def multiply(x: int, y: int) -> int:
return x * y
- Annotation processing adds minimal runtime overhead
- Use static type checkers for compile-time validation
- Balance between type safety and performance
Complex Annotation Patterns
from typing import Literal, TypedDict
class UserConfig(TypedDict):
username: str
role: Literal['admin', 'user', 'guest']
permissions: List[str]
def configure_user(config: UserConfig) -> None:
## User configuration logic
pass
By mastering these advanced annotation techniques, developers can create more robust, self-documenting, and type-safe Python code.