Decorators and metadata in Python let you attach arbitrary additional information (metadata) to functions, methods, or classes without modifying their source code — using decorators to add attributes like author, version, description, deprecation status, or custom tags. This is useful for documentation, runtime introspection, testing frameworks, API generation, logging, or validation. In 2026, decorator-based metadata is common in libraries (FastAPI, Pydantic, Typer, pytest plugins), data pipelines (marking functions for Polars/pandas processing), ML workflows (tagging models/losses), and production systems where introspection or auto-discovery is needed. It keeps code clean, self-documenting, and extensible while separating metadata from implementation.
Here’s a complete, practical guide to using decorators for metadata in Python: basic attachment, parameterized metadata, accessing metadata, real-world patterns, and modern best practices with type hints, functools.wraps, runtime inspection, and pandas/Polars integration.
Basic metadata decorator — adds a simple attribute to the function.
def add_metadata(meta: dict):
def decorator(func):
func.meta = meta # attach metadata as attribute
return func
return decorator
@add_metadata({'author': 'Jane Doe', 'version': '1.0', 'description': 'Computes sum'})
def add(a: int, b: int) -> int:
return a + b
print(add.meta) # {'author': 'Jane Doe', 'version': '1.0', 'description': 'Computes sum'}
print(add(3, 4)) # 7 (original function unchanged)
Preserving function metadata — use @wraps to copy name, docstring, annotations — otherwise they are lost.
from functools import wraps
def add_metadata(meta: dict):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.meta = meta
return wrapper
return decorator
@add_metadata({'role': 'transformer', 'priority': 10})
def normalize(col):
"""Normalize column to zero mean/unit variance."""
return (col - col.mean()) / col.std()
print(normalize.__name__) # normalize (not wrapper)
print(normalize.__doc__) # Normalize column to zero mean/unit variance.
print(normalize.meta) # {'role': 'transformer', 'priority': 10}
Real-world pattern: metadata for pandas/Polars transformations — tag functions for auto-discovery, validation, or pipeline orchestration.
def register_transformation(meta: dict):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.meta = meta
wrapper.meta['name'] = func.__name__
return wrapper
return decorator
@register_transformation({'type': 'cleaning', 'priority': 1, 'description': 'Drop nulls'})
def drop_nulls(df: pd.DataFrame) -> pd.DataFrame:
return df.dropna()
@register_transformation({'type': 'feature', 'priority': 2, 'description': 'Normalize values'})
def normalize(df: pd.DataFrame) -> pd.DataFrame:
return (df - df.mean()) / df.std()
# Auto-register and sort transformations
transformations = [drop_nulls, normalize]
sorted_transforms = sorted(transformations, key=lambda f: f.meta['priority'])
df = pd.DataFrame({'A': [1, None, 3], 'B': [4, 5, None]})
for func in sorted_transforms:
print(f"Applying {func.meta['description']}")
df = func(df)
print(df)
Best practices make decorator metadata safe, readable, and powerful. Always use @wraps(func) — preserves name, docstring, type hints, annotations. Store metadata as func.meta = dict — simple, introspectable attribute. Modern tip: use Polars for lazy transformations — attach metadata to expressions or lazy frames for pipeline introspection. Add type hints — def decorator(func: Callable) -> Callable — improves clarity and mypy checks. Use typing.Annotated or dataclasses — for structured metadata. Use __annotations__ — combine with type hints for richer info. Use inspect — inspect.getmembers(func) to discover metadata at runtime. Use dataclasses for metadata — @dataclass frozen=True for immutable tags. Avoid deep wrapping — too many decorators slow calls; profile with timeit. Test metadata — assert func.meta['key'] == value in tests. Combine with pydantic — validate metadata structures. Use typer/fastapi patterns — metadata for CLI/API auto-docs. Use contextvars — per-context metadata in async code.
Decorators with metadata add attributes (author, version, tags) to functions/classes — using @wraps, type hints, structured dicts/dataclasses, and runtime introspection. In 2026, use them for documentation, discovery, validation, pipeline orchestration, and Polars/pandas tagging. Master decorator metadata, and you’ll write self-documenting, extensible, introspectable Python code that adapts to tools and frameworks automatically.
Next time you want documentation or tags on a function — attach metadata with a decorator. It’s Python’s cleanest way to say: “This function has extra info — discover it at runtime.”