PYTHON & CODING · QUESTION 78
What is a decorator in Python, and how does it work?
On this page
A decorator is a callable that takes a function or class and returns a replacement, often wrapping the original with additional behavior. The `@decorator` syntax applies that transformation at definition time. Decorators are useful for cross-cutting concerns such as logging, caching, or authorization; use `functools.wraps` so wrapped functions preserve useful metadata.
LET'S MAKE IT SIMPLE
First, the idea in plain English
Functions are values in Python, so one function can receive another function and return a new one. The new function can do something before or after calling the original. The `@` notation is a compact way to apply this wrapper where the function is defined.
A USEFUL COMPARISON
Picture it this way
A decorator is like putting a reusable cover around a tool: the tool still does its original job, while the cover adds a safety label or tracking step. The wrapper must preserve how the tool is identified and called, or it can surprise other code.
FOLLOW THE JOURNEY
A wrapper adds behavior around a function call
Trace definition-time decoration and runtime calls through a wrapper.
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; Cross-cutting logic is reused without editing every function body.
A function is passed to a decorator, which returns a wrapper; calls enter the wrapper, run added behavior, invoke the original function, and return its result.
How to read this visual
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; Cross-cutting logic is reused without editing every function body.
UNDER THE HOOD
How it works, step by step
Define original function
Create a function object with its behavior and metadata.
Why it matters: The decorator receives this callable.
Apply decorator
Pass the function to a callable that returns a replacement.
Why it matters: The replacement can wrap or transform the original.
Call wrapper
Run added behavior and delegate to the original function.
Why it matters: Cross-cutting logic is reused without editing every function body.
OUTSIDE THE TEXTBOOK
A practical example
A timing decorator records how long a function takes. The wrapper captures a start time, calls the original function, records elapsed time, and returns the original result. It should use `try/finally` if timing must be recorded even when the function raises an exception.
GO A LITTLE DEEPER
Technical details that matter
- Decorators can accept configuration by using an outer function that returns the actual decorator.
- Decorators are applied from the closest `@` line outward when stacked.
- Use `functools.wraps` to copy name, documentation, and annotations to the wrapper.
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):
return f"Hello, {name}"`@announce` is equivalent to `greet = announce(greet)`. The wrapper accepts the original function’s arguments and delegates to it.
MAKE A GOOD CHOICE
When is this useful?
- Applying logging, timing, caching, or authorization consistently.
- Adding reusable behavior around framework callbacks.
- Composing function transformations declaratively.
Use an explicit helper or context manager when the behavior is local or needs a visible lifetime. Avoid decorators when they hide important control flow.
WATCH OUT FOR
Common misunderstandings
- Forgetting to return the wrapped function’s result.
- Dropping metadata by omitting `functools.wraps`.
- Assuming decorators run on every call rather than when the function is defined.
REAL-WORLD TRADE-OFFS
Limitations and failure cases
- Nested wrappers can make debugging and stack traces harder.
- Decorators can hide side effects or alter signatures if metadata is not preserved.
- Order matters when multiple decorators are stacked.
WHAT TO REMEMBER
Key takeaways
- 1A decorator transforms a function or class.
- 2The `@` syntax applies it at definition time.
- 3Use `wraps` and preserve arguments, return values, and exceptions.
PRACTICE THE FOLLOW-UP
What might an interviewer ask next?
When does a decorator execute?
The decorator is applied when the decorated function or class definition executes, usually during module import; the returned wrapper runs when called.
Why use `functools.wraps`?
It preserves useful metadata such as the original function’s name and documentation and provides a `__wrapped__` reference.
SAY IT OUT LOUD
How to explain this in an interview
“A decorator is a callable that takes a function and returns a replacement, commonly a wrapper that adds behavior before or after calling the original. The `@` syntax applies it when the function is defined. I use decorators for reusable cross-cutting logic like timing or caching, and preserve metadata with `functools.wraps`.”
Connect this question to the roadmap topic it supports.