← Python lessons

Python Decorators Explained: @ Syntax and functools.wraps

A Python decorator takes a function and returns a replacement. Use @ syntax to apply it when defining a function, often adding behavior around each call.

Read First-class functions and closures first.

Wrap a function

The decorator creates a wrapper that calls the original function. Return the wrapper without calling it.

def announce(function):
    def wrapper():
        print("Starting")
        return function()
    return wrapper

def greet():
    return "Hello"

greet = announce(greet)
print(greet())

Calling the replacement prints Starting, then returns Hello for the outer print().

Apply a decorator with @ syntax

Place @announce directly above the function definition. It has the same effect as greet = announce(greet).

def announce(function):
    def wrapper():
        print("Starting")
        return function()
    return wrapper

@announce
def greet():
    return "Hello"

print(greet())

The output is again Starting, then Hello. Decoration happens at definition time; the wrapper runs when called.

Forward arguments and preserve metadata with wraps

*args collects positional arguments; **kwargs collects keyword arguments. Pass both on and return the original result.

from functools import wraps

def announce(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print("Starting")
        return function(*args, **kwargs)
    return wrapper

@announce
def greet(name, punctuation="!"):
    """Build a greeting."""
    return f"Hello, {name}{punctuation}"

print(greet("Ada", punctuation="?"))
print(greet.__name__)
print(greet.__doc__)

wraps() preserves metadata such as the original name and docstring. Without it, the name would be wrapper.

See also: Parameters and return, First-class functions and closures.

Your turn

Apply @double_result to points, then print the result of calling points().

from functools import wraps

def double_result(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        return function(*args, **kwargs) * 2
    return wrapper

# add one line here: decorate points with double_result
def points():
    return 7

# add one line here: print the result of calling points()

Expected output

14

Practise this in the free Python course, where every exercise checks your code instantly.