What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

What does the @ symbol do above a Python function? It applies a decorator to the function object created by the definition, then binds the decorator’s result to the function’s name. In the common “gift wrapper” pattern, that result is a new callable that adds behavior around the original. But decorators can return other callables—or even non-callable objects—so wrapping is a common technique, not the whole definition.

What a decorator does

Think of a function as a gift and a decorator as an extra layer that changes how the gift is presented or used. The analogy is useful as long as it does not suggest that every decorator edits a function in place or calls the original function.

Python’s language reference says, “A function definition may be wrapped by one or more decorator expressions.” In practical terms, Python creates the function object, evaluates the decorator expression, applies the resulting decorator to the function, and binds the returned object to the function’s name. The reference describes this syntax in Python 3.14’s “Compound statements”.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A useful mental model for a single decorator is:

function_name = decorator(function_name)

This is an equivalent assignment model, not a claim that Python literally rewrites the source or executes the definition line by line in this form. The official PEP 318 uses this equivalence to explain decorator syntax.

How a wrapper adds behavior

A typical decorator returns a wrapper function. The wrapper can do work before or after calling the original function, forward its arguments, and return its result.

from functools import wraps

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Starting")
        result = func(*args, **kwargs)
        print("Finished")
        return result
    return wrapper

@announce
def greet(name):
    return f"Hello, {name}!"

When Python executes the decorated definition, it passes the function object for greet to announce and binds the returned wrapper to the name greet. Later, calling greet("Ada") calls that wrapper. The wrapper prints “Starting,” calls the original function with the supplied argument, prints “Finished,” and returns the original result, "Hello, Ada!".

The same relationship can be shown without @ syntax:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def greet(name):
    return f"Hello, {name}!"

greet = announce(greet)

This second form demonstrates the effect of decorator syntax; it is not a recommendation to rewrite decorated definitions manually.

When decoration happens—and when wrapper code runs

These are separate stages. The decorator is applied when Python executes the function definition. Code inside a returned wrapper runs later, when the decorated name is called. In the example, announce runs during definition, while the two print statements run on each call to greet.

This distinction matters when a decorator performs setup or replaces a function: that work happens during definition, not automatically each time the decorated function is called. Call-time behavior belongs in the returned callable, if the decorator returns one.

Why functools.wraps belongs on wrappers

A plain wrapper has its own name and docstring, which can obscure the original function’s visible metadata. Apply @wraps(func) to the inner wrapper to preserve useful metadata from func. The standard-library documentation also describes the wrapped callable as accessible through __wrapped__. See the Python 3.14.8 functools documentation.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For wrappers that call the original function, return its result unless changing the return behavior is intentional. Omitting return result in the example would make the decorated function return None instead of the greeting.

How stacked decorators are applied

With multiple decorators, the one closest to def is applied first. The decorator above it receives that result:

@outer
@inner
def work():
    ...

# Conceptually:
work = outer(inner(work))

So inner is applied to the original function first, then outer is applied to the result. When work is called later, the object returned by outer is the callable reached through that name (if it is callable). The order of application is defined by the syntax; what happens during a call depends on what each decorator returns.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What changes with a decorator factory

When a decorator needs configuration, the expression after @ can call a factory that produces the decorator:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@repeat(3)
def wave():
    ...

Here, repeat(3) runs first and returns a decorator. That returned decorator then receives the function object for wave. The integer 3 is passed to the factory; it is not passed directly to wave by the decorator syntax. PEP 318 discusses this pattern along with stacked decorators.

Three decorator forms at a glance

Form What receives what Result
@decorate decorate receives the function object created by the definition. The decorated name refers to the object returned by decorate.
@factory(options) The factory receives its options and returns a decorator; that decorator receives the function object. The decorated name refers to the object returned by the decorator.
@outer above @inner inner receives the function first; outer receives the result. Conceptually, the name is bound to outer(inner(function)).

Common mistakes to avoid

  • Assuming every decorator is a wrapper. A decorator may return a different callable or another kind of object; it need not call the original.
  • Mixing up definition time and call time. Applying the decorator and running the wrapper body are distinct events.
  • Reversing stacked order. The decorator nearest the definition is applied first.
  • Dropping the original result. A wrapper that delegates should usually return the delegated call’s result.
  • Forgetting metadata. Use functools.wraps for ordinary function wrappers when retaining the original function’s metadata is useful.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.