0
0
PythonHow-ToBeginner · 3 min read

How to Use functools.wraps in Python: Simple Guide

Use functools.wraps as a decorator inside your custom decorator to copy the original function's name, docstring, and other metadata. This keeps the wrapped function's identity intact, which helps with debugging and documentation.
📐

Syntax

The functools.wraps decorator is used inside your decorator function to wrap the original function. It takes the original function as an argument and returns a decorator that updates the wrapper function's metadata.

Basic syntax:

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # code before
        result = func(*args, **kwargs)
        # code after
        return result
    return wrapper
python
from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # code before
        result = func(*args, **kwargs)
        # code after
        return result
    return wrapper
💻

Example

This example shows a decorator that prints a message before and after calling the original function. Using @wraps(func) keeps the original function's name and docstring.

python
from functools import wraps

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}...")
        result = func(*args, **kwargs)
        print(f"Finished {func.__name__}.")
        return result
    return wrapper

@announce
def greet(name):
    """Greet someone by name."""
    print(f"Hello, {name}!")

print(greet.__name__)
print(greet.__doc__)
greet("Alice")
Output
greet Greet someone by name. Calling greet... Hello, Alice! Finished greet.
⚠️

Common Pitfalls

Without using functools.wraps, the wrapper function replaces the original function's metadata. This causes issues like losing the original function's name and docstring, which can confuse debugging and documentation tools.

Wrong way (missing @wraps):

python
def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator
def example():
    """Original docstring."""
    pass

print(example.__name__)  # Outputs 'wrapper'
print(example.__doc__)   # Outputs None

# Correct way with wraps:
from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator
def example():
    """Original docstring."""
    pass

print(example.__name__)  # Outputs 'example'
print(example.__doc__)   # Outputs 'Original docstring.'
Output
wrapper None example Original docstring.
📊

Quick Reference

  • Purpose: Preserve original function metadata in decorators.
  • Import: from functools import wraps
  • Usage: Place @wraps(original_function) above the wrapper function inside your decorator.
  • Benefits: Keeps function name, docstring, annotations, and module info intact.

Key Takeaways

Always use functools.wraps inside decorators to keep the original function's metadata.
Without wraps, the wrapper function hides the original function's name and docstring.
Place @wraps(func) directly above the wrapper function inside your decorator.
Using wraps helps debugging, documentation, and tools that rely on function metadata.