What is a Decorator in Python?
Learn what a Python decorator is, how the @syntax works, functools.wraps, decorators with arguments, and real examples like caching and logging.
Expected Interview Answer
A decorator is a function that takes another function (or class) as input and returns a new, wrapped version of it, letting you add behavior like logging, timing, or access control around a function without changing its source code, applied with the @decorator_name syntax placed above a function definition.
Under the hood, @my_decorator above def foo(): ... is exactly equivalent to foo = my_decorator(foo); the decorator receives the original function, defines an inner wrapper function that adds behavior before and/or after calling it, and returns that wrapper. Because the wrapper replaces the original function's name in the namespace, well-written decorators use functools.wraps to preserve the original function's __name__, __doc__, and metadata. Decorators can accept their own arguments by adding an extra layer of nesting (a decorator factory that returns the actual decorator), and multiple decorators can be stacked, applying bottom-up. Common real-world uses include Flask/FastAPI route registration, @property for computed attributes, @staticmethod/@classmethod, retry logic, caching (functools.lru_cache), and logging/timing wrappers.
- Adds cross-cutting behavior (logging, timing, auth) without touching the original code
- Keeps business logic and infrastructure concerns separated
- Reusable across many functions with a single @decorator line
- Composable — decorators can be stacked for layered behavior
- Widely used in real frameworks (Flask routes, functools.lru_cache, @property)
AI Mentor Explanation
A decorator is like adding a reviewing umpire's protocol on top of the on-field umpire's decision — the original umpire still makes the call, but the DRS layer wraps it with extra checks before the final verdict is confirmed. You attach this review layer with a single flag at the start of the match, and it applies to every decision without rewriting how umpires judge each ball.
Step-by-Step Explanation
Step 1
Function as input
A decorator is a function that receives another function as its single argument.
Step 2
Inner wrapper
Inside, it defines a wrapper function that calls the original and adds behavior before/after.
Step 3
Return the wrapper
The decorator returns the wrapper, which replaces the original name via @decorator syntax.
Step 4
@syntax is sugar
@my_decorator above def foo(): is equivalent to foo = my_decorator(foo).
Step 5
Preserve metadata
Use functools.wraps(func) on the wrapper so __name__ and __doc__ stay correct.
Step 6
Decorators with arguments
A decorator factory adds one more nesting level to accept its own arguments, e.g. @retry(times=3).
What Interviewer Expects
- Explains a decorator as a higher-order function wrapping another function
- Can show the @syntax is equivalent to foo = decorator(foo)
- Knows to use functools.wraps to preserve metadata
- Can name real examples: @property, @staticmethod, @lru_cache, Flask routes
- Can explain how a decorator with its own arguments needs an extra nesting level
Common Mistakes
- Forgetting functools.wraps, which breaks introspection and debugging
- Confusing decorator order when stacking multiple decorators (they apply bottom-up)
- Not returning the wrapper function from the decorator
- Assuming decorators only apply to functions, forgetting classes can be decorated too
Best Answer (HR Friendly)
“A decorator is a way to add extra behavior to a function — like logging, timing, or security checks — without modifying the function's own code. You just add a single line with an @ symbol above the function, and Python automatically wraps it with that extra behavior every time it runs.”
Code Example
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timed
def slow_add(a, b):
time.sleep(0.1)
return a + b
print(slow_add(2, 3))
# slow_add took 0.1002s
# 5Follow-up Questions
- How would you write a decorator that itself accepts arguments?
- Why is functools.wraps important when writing decorators?
- What order do multiple stacked decorators apply in?
- How does @property use the decorator mechanism?
- Can you write a class-based decorator instead of a function-based one?
MCQ Practice
1. What does @my_decorator placed above def foo(): do?
The @ syntax is sugar for reassigning foo to the result of calling my_decorator(foo).
2. What is functools.wraps used for inside a decorator?
functools.wraps copies metadata like __name__ and __doc__ from the original function onto the wrapper.
3. When two decorators are stacked (@a then @b above a function), in what order are they applied?
The decorator closest to the function (b) wraps it first, then a wraps the result, so execution effectively applies bottom-up.
Flash Cards
What is a Python decorator? — A function that takes a function and returns a wrapped version adding extra behavior.
@decorator syntax is equivalent to what? — foo = decorator(foo)
Why use functools.wraps in a decorator? — To preserve the wrapped function's __name__, __doc__, and metadata.
Name two built-in decorators. — @property and @staticmethod (also @classmethod, @functools.lru_cache).