← Back to Python Course | Chapter 11: Advanced Python | Lesson 3 of 12

Python Decorators

A decorator is like gift wrap for a function: it adds something extra around it without changing the gift inside. You put it on with an at-sign above the function.
Syntax
python
def decorator_name(func):
    def wrapper(*args, **kwargs):
        # before
        result = func(*args, **kwargs)
        # after
        return result
    return wrapper

@decorator_name
def function_name():
    pass

What is a Decorator?

A decorator is a function that wraps another function, adding behavior before or after the original runs, without permanently modifying the original function's own code -- it takes a function in and returns a (usually new, wrapped) function out.

Example: What is a Decorator?

python
def shout(func):
    def wrapper():
        print(func().upper())  # extra behavior added around the original call
    return wrapper

@shout
def greet():
    return "hello"

greet()  # actually calls wrapper(), not the original greet

Designing a Basic Decorator

Applying a decorator manually just means calling it with your target function and using whatever it returns: 'wrapped = my_decorator(original_function)' -- understanding this manual form is what makes the @ shorthand's actual behavior clear rather than feeling like magic.

Example: Designing a Basic Decorator

python
def shout(func):
    def wrapper():
        print(func().upper())
    return wrapper

def greet():
    return "hello"

wrapped = shout(greet)  # manually applying the decorator, same as @shout
wrapped()

Decorating with Arguments

Because the function being decorated might accept any combination of arguments, a general-purpose wrapper collects them with *args and **kwargs and forwards them unchanged to the original function -- without this, a decorator would only work on functions with one exact, fixed signature.

Example: Decorating with Arguments

python
def log(func):
    def wrapper(*args, **kwargs):  # accepts any arguments the original function takes
        print("Calling", func.__name__)
        return func(*args, **kwargs)  # forwards them unchanged
    return wrapper

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

print(add(2, 3))

The @ Syntax

Writing '@my_decorator' directly above a function definition is exactly equivalent to writing 'my_function = my_decorator(my_function)' right after it -- the @ syntax is pure convenience, not a different mechanism underneath.

Example: The @ Syntax

python
def shout(func):
    def wrapper():
        return func().upper()
    return wrapper

@shout
def greet():
    return "hi"

print(greet())  # same as greet = shout(greet)

Multiple Decorators

Stacking multiple decorators on one function applies them from the bottom up -- the decorator closest to the function wraps it first, and each decorator above that wraps the result of the one below it, so execution order matters when the decorators interact.

Example: Multiple Decorators

python
def bold(func):
    def wrapper():
        return "**" + func() + "**"
    return wrapper

def shout(func):
    def wrapper():
        return func().upper()
    return wrapper

@bold
@shout  # applied first, closest to the function
def greet():
    return "hi"

print(greet())
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting to return the inner wrapper function from the decorator, so the decorated name becomes None.
  2. Not passing *args, **kwargs through the wrapper, so the decorator breaks functions with arguments.
  3. Forgetting functools.wraps, so the decorated function loses its __name__ and docstring.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.