PythonFunctionsAdvanced

Decorators

A syntactic sugar for wrapping a function or method with another function, modifying its behavior without altering its original code.

Review the syntaxStudy the examplesOpen the coding app
@decorator_name
def func_to_decorate():
    pass

This static page keeps the syntax and examples indexed for search, while the coding app handles interactive exploration and saved references.

What it does

Overview

A syntactic sugar for wrapping a function or method with another function, modifying its behavior without altering its original code.

Decorators in Python are a powerful and elegant way to extend or modify the behavior of functions or classes. Essentially, a decorator is a callable that takes another function as an argument, adds some functionality, and returns a new function (or the modified original). The `@ame` syntax is syntactic sugar for `ecorate = ame(ecorate)`. This means that the decorator function is executed *at definition time* of the decorated function. Common use cases for decorators include logging function calls, timing execution, enforcing authentication/authorization, caching results, or registering functions. A typical decorator involves an outer function that takes the function to be decorated, and an inner wrapper function that contains the added logic and calls the original function. Using `functools.wraps` inside the wrapper is a best practice to preserve the original function's metadata (like `__name__`, `__doc__`, `__module__`). Decorators promote the Don't Repeat Yourself (DRY) principle and improve code modularity by separating concerns. While powerful, overly complex decorators can make debugging challenging. The time complexity added by a decorator is typically negligible, usually O(1) per function call, plus the complexity of the added logic. Best practices include keeping decorators focused on a single responsibility, using `functools.wraps`, and ensuring they don't obscure the core logic of the decorated function.

Quick reference

Syntax

@decorator_name
def func_to_decorate():
    pass

See it in practice

Examples

1

A simple logging decorator

import functools

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

@log_calls
def multiply(x, y):
    return x * y

add(2, 3)
multiply(a=5, y=4)
Output:
Calling add with args: (2, 3), kwargs: {} add returned: 5 Calling multiply with args: (), kwargs: {'a': 5, 'y': 4} multiply returned: 20

The `alls` decorator wraps `add` and `multiply`. Before and after each call, it prints information about the function, its arguments, and its return value, without modifying the core logic of `add` or `multiply`.

2

A decorator with arguments

import functools

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}!")

greet("Alice")
Output:
Hello Alice! Hello Alice! Hello Alice!

Decorators can accept arguments by using an outer function that takes the arguments and returns the actual decorator function. Here, `greet` is called 3 times.

3

Using multiple decorators

import functools

def uppercase_result(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()
    return wrapper

def add_exclamation(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) + '!'
    return wrapper

@add_exclamation
@uppercase_result
def get_message(name):
    return f"Hello {name}"

print(get_message("Bob"))
Output:
HELLO BOB!

Decorators can be stacked. They are applied from bottom to top (closest to the function first). So `esult` runs first, then `xclamation`.

Debug faster

Common Errors

1

Forgetting `functools.wraps`

Cause: Without `functools.wraps`, the decorated function loses its original name, docstring, and other metadata, making debugging harder.

Fix: Always use `@functools.wraps(func)` on the inner wrapper function within your decorator.

def simple_decorator(func):
    def wrapper(): # Missing @functools.wraps(func)
        return func()
    return wrapper

@simple_decorator
def my_func():
    """My docstring"""
    pass
# print(my_func.__name__) # Would print 'wrapper' instead of 'my_func'

Runtime support

Compatibility

AnyN/APython 2.4+

Source: Python Docs

Common questions

Frequently Asked Questions

A syntactic sugar for wrapping a function or method with another function, modifying its behavior without altering its original code.

Forgetting `functools.wraps`: Always use `@functools.wraps(func)` on the inner wrapper function within your decorator.