Decorators¶
What is a Decorator?¶
A decorator is simply a function that takes another function as input, adds some additional behavior, and returns a new function.
One of the biggest reasons decorators work is because functions are first-class objects and closures remember variables from their enclosing scope.
A decorator doesn't modify the original function. Instead, it wraps the original function inside another function.
How Does a Decorator Work?¶
Consider a simple function.
Now suppose we want to log every time this function is called.
Without decorators,
This quickly becomes repetitive.
Instead, we can write a decorator.
Now decorate the function.
Calling
is actually equivalent to
Output
How Python Executes a Decorator¶
When Python sees
Python internally converts it into
The name greet no longer points to the original function.
Instead,
Why Does This Work?¶
The decorator returns the wrapper function.
The wrapper function is a closure.
It remembers the original function through its closure.
logger(func)
│
▼
Wrapper Function Object
+---------------------------+
| Bytecode |
| Parameters |
| Closure |
+-------------+-------------+
│
▼
func ─────────► greet()
Even after logger() finishes executing, the wrapper still remembers which function to execute.
That's why decorators are built on top of closures.
Decorators With Arguments¶
Most decorators need to accept arbitrary arguments.
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
This allows the decorator to wrap any function regardless of its parameters.
Example
Output
Preserving Function Metadata¶
One common issue is that decorators replace the original function.
Output
Instead of
To preserve metadata, Python provides functools.wraps.
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
Now
Output
Parameterized Decorators¶
Sometimes decorators themselves need arguments.
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
Usage
Output
Real-World Examples¶
Flask¶
Registers an endpoint.
FastAPI¶
Registers an HTTP GET endpoint.
Authentication¶
Checks authentication before executing the function.
Timing¶
Measures execution time.
Logging¶
Logs every function call.
Follow-up Questions¶
What is a decorator?¶
A decorator is a function that takes another function, extends its behavior, and returns a new function.
Why do decorators work?¶
Because functions are first-class objects and closures preserve references to the original function.
What does the @ syntax do?¶
It is syntactic sugar for
Why do decorators usually use *args and **kwargs?¶
To support functions with any number of positional and keyword arguments.
Why do we use functools.wraps?¶
To preserve the original function's metadata such as __name__, __doc__, and annotations.
Can a decorator take parameters?¶
Yes.
Parameterized decorators return another decorator function.
Common Interview Mistakes¶
❌ Thinking decorators modify the original function.
✔ They return a new wrapper function.
❌ Forgetting *args and **kwargs.
✔ Without them, the decorator only works for specific function signatures.
❌ Forgetting functools.wraps.
✔ Without it, function metadata is lost.
Interview Tip¶
When asked about decorators, don't stop at saying:
"A decorator adds extra functionality."
A stronger answer is:
"A decorator is a higher-order function that takes a function object, creates a wrapper function using a closure, and returns that wrapper. The
@syntax is simply syntactic sugar for reassigning the original function to the wrapper returned by the decorator."
That answer naturally connects first-class functions, closures, and decorators.
Key Takeaways¶
- Functions are first-class objects.
- Decorators take a function and return another function.
- Decorators rely on closures to remember the original function.
@decoratoris syntactic sugar forfunc = decorator(func).- Use
*argsand**kwargsfor generic decorators. - Use
functools.wrapsto preserve function metadata. - Decorators are heavily used in Flask, FastAPI, Django, logging, authentication, caching, and profiling.