Renaming decorated functions is a crucial best practice when using decorators in Python — without proper care, the wrapped function loses its original identity (__name__, docstring, type hints, annotations), causing confusion during debugging, profiling, logging, or introspection (e.g., print(func.__name__) shows wrapper instead of the real name). The @functools.wraps decorator solves most of this by copying metadata from the original function to the wrapper, while manual renaming of __name__ or other attributes adds extra clarity (e.g., decorated_my_func). In 2026, metadata preservation is non-negotiable — modern tools (mypy, IDEs, logging, monitoring) rely on accurate function names and signatures, and libraries like FastAPI, Typer, Pydantic, and pytest expect proper metadata for auto-docs, validation, and test discovery.
Here’s a complete, practical guide to renaming and preserving metadata for decorated functions in Python: why it matters, @wraps basics, manual renaming, advanced metadata (docstrings, annotations), real-world patterns, and modern best practices with type hints, logging, profiling, and pandas/Polars integration.
Without preservation — wrapper overwrites original metadata, breaking introspection.
def simple_decorator(func):
def wrapper(*args, **kwargs):
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper
@simple_decorator
def add(a: int, b: int) -> int:
"""Adds two numbers."""
return a + b
print(add.__name__) # wrapper (lost!)
print(add.__doc__) # None (lost!)
print(add.__annotations__) # {} (lost!)
Using @functools.wraps — copies __name__, __doc__, __module__, __annotations__, and more automatically.
from functools import wraps
def better_decorator(func):
@wraps(func) # preserves metadata
def wrapper(*args, **kwargs):
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper
@better_decorator
def add(a: int, b: int) -> int:
"""Adds two numbers."""
return a + b
print(add.__name__) # add (preserved!)
print(add.__doc__) # Adds two numbers.
print(add.__annotations__) # {'a': , 'b': , 'return': }
Manual renaming — customize __name__ after @wraps for extra clarity (e.g., prefix/suffix).
def logging_decorator(prefix: str = "log"):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix} calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{prefix} finished {func.__name__}")
return result
# Manual rename after wraps
wrapper.__name__ = f"{prefix}_{func.__name__}"
return wrapper
return decorator
@logging_decorator("debug")
def multiply(x: float, y: float) -> float:
return x * y
print(multiply.__name__) # debug_multiply
multiply(2, 3)
# debug calling multiply
# debug finished multiply
Real-world pattern: preserving metadata in pandas/Polars pipelines — decorate transformation functions while keeping original names/docs.
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
import time
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {end - start:.4f}s")
return result
return wrapper
@timing_decorator
def normalize(df: pd.DataFrame) -> pd.DataFrame:
"""Normalize numeric columns."""
return (df - df.mean()) / df.std()
df = pd.DataFrame({'A': range(1000)})
normalized = normalize(df)
print(normalize.__name__) # normalize (preserved)
print(normalize.__doc__) # Normalize numeric columns.
Best practices make decorated function metadata safe, readable, and useful. Always use @wraps(func) — preserves most attributes automatically. Modern tip: use Polars — decorate lazy expressions or custom functions; Polars preserves metadata well. Add type hints — def decorator(func: Callable[P, R]) -> Callable[P, R] — preserves signatures. Manually set __name__ — for clarity (e.g., logged_func). Copy docstrings explicitly — wrapper.__doc__ = func.__doc__ if @wraps misses something. Preserve __annotations__ — wrapper.__annotations__ = func.__annotations__. Use update_wrapper — low-level alternative to @wraps. Test metadata — assert wrapped.__name__ == original.__name__. Use inspect.signature — verify preserved parameters. Combine with @dataclass or pydantic — attach structured metadata. Use typer/fastapi patterns — metadata for auto-docs/CLI. Avoid overwriting __name__ too aggressively — keep recognizable. Use __qualname__ — for nested functions/methods. Monitor in production — logging frameworks use func.__name__ for traces.
Renaming decorated functions with @wraps and manual __name__ adjustments preserves original identity — essential for debugging, profiling, logging, and introspection. In 2026, always use @wraps, type hints, manual renames for clarity, Polars compatibility, and test metadata preservation. Master this pattern, and you’ll write decorators that stay transparent, debuggable, and production-ready.
Next time you write a decorator — preserve and rename properly. It’s Python’s cleanest way to say: “This wrapped function should still know who it is.”