2025, Nov 18 17:00

How to Use functools.wraps in Python Decorator Factories: Correct Wrapper Placement with Example

Learn where to apply functools.wraps in a Python decorator factory. See correct placement on the inner wrapper function with a clear example to avoid confusion

Decorator factories are a great way to parameterize behavior, but they often raise a simple, recurring question: where exactly should functools.wraps be applied? If you’ve seen only the plain decorator examples in the official docs, it’s reasonable to hesitate when a factory enters the picture.

Problem statement

Consider a decorator factory that multiplies a list of numbers, prints the result, and then calls the wrapped function. The structure is correct, but the placement of @wraps isn’t obvious if you’re new to factories.

from functools import wraps

def product_chain(values):
    def apply_to(func_obj):
        def inner_exec(*args, **kwargs):
            acc = values[0]
            for nxt in values[1:]:
                acc = acc * nxt
            print(acc)
            return func_obj(*args)
        return inner_exec
    return apply_to

@product_chain(values=[1, 2, 3])
def announce(**kwargs):
    print("That was the multiplied number.")

announce()

What’s going on

@wraps is itself a decorator. It’s imported from functools and placed directly on the inner wrapper function. While the documentation commonly shows this for a regular decorator, the same placement applies when you have a decorator factory: you still decorate the wrapper that gets returned.

The fix

Import wraps from functools and apply it to the wrapper function inside the factory, passing the original function to it.

from functools import wraps

def product_chain(values):
    def apply_to(func_obj):
        @wraps(func_obj)
        def inner_exec(*args, **kwargs):
            acc = values[0]
            for nxt in values[1:]:
                acc = acc * nxt
            print(acc)
            return func_obj(*args)
        return inner_exec
    return apply_to

@product_chain(values=[1, 2, 3])
def announce(**kwargs):
    print("That was the multiplied number.")

announce()

Why this placement matters

Using @wraps on the wrapper function in a decorator factory matches how it’s used in regular decorators and removes the uncertainty about “which function” it should decorate. It aligns with the official guidance and behaves consistently across both patterns.

Conclusion

When working with a decorator factory, import wraps from functools and apply it to the inner wrapper that you return. Place @wraps(original_function) directly above the wrapper definition. This pattern is the same as in regular decorators and helps keep usage straightforward and predictable.