1. Introduction
A decorator is a function that takes another function as input and returns a new function that adds extra behavior around it, without modifying the original function's source code. Decorators are a powerful way to reuse cross-cutting logic, such as logging, timing, caching, or access control, across many functions.
Cricket analogy: A decorator is like adding a third umpire's review overlay to every delivery without changing how the bowler actually bowls—it wraps the action with extra scrutiny like timing or logging, reusable across every over.
Python provides special '@' syntax to apply a decorator to a function definition, making the wrapping explicit and easy to read at the point where the function is declared.
Cricket analogy: The "@" syntax for decorators is like a scorecard annotation placed right next to a batsman's name noting "review pending," making the extra behavior visible exactly where the player is declared in the lineup.
2. Syntax
def my_decorator(func):
def wrapper(*args, **kwargs):
# code before calling the original function
result = func(*args, **kwargs)
# code after calling the original function
return result
return wrapper
@my_decorator
def greet(name):
return f'Hello, {name}!'
# The above is equivalent to:
# def greet(name):
# return f'Hello, {name}!'
# greet = my_decorator(greet)3. Explanation
Writing '@my_decorator' above a function definition is shorthand for 'greet = my_decorator(greet)': the original function is passed into the decorator, and whatever the decorator returns replaces the original name. The inner 'wrapper' function typically accepts *args and **kwargs so it can forward any arguments to the wrapped function, regardless of its signature.
Cricket analogy: "@my_decorator" reassigning greet is like team management deciding a bowler is now "the death-over specialist," replacing his general role with a specialized one that still accepts any batsman lineup thrown at him, like *args/**kwargs.
A common pitfall is that the wrapper function replaces the original function's identity, so introspection attributes like __name__ and __doc__ end up pointing to 'wrapper' instead of the original function. The functools.wraps decorator, applied to the wrapper, copies over these metadata attributes to fix this.
Cricket analogy: Without functools.wraps, a scorecard would list a substitute's name instead of the original player's, even though the substitute is filling in—wraps fixes this so the "credit" (name, stats) stays with the real player.
Without functools.wraps, a decorated function's __name__, __doc__, and other metadata get silently replaced with the wrapper's own values, which can break tools that rely on introspection (like debuggers or documentation generators). Always apply @functools.wraps(func) to the inner wrapper function.
4. Example
import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__} with {args}')
result = func(*args, **kwargs)
print(f'{func.__name__} returned {result}')
return result
return wrapper
@log_call
def add(a, b):
"""Add two numbers."""
return a + b
total = add(3, 4)
print(total)
print(add.__name__)
print(add.__doc__)5. Output
Calling add with (3, 4)
add returned 7
7
add
Add two numbers.6. Key Takeaways
- A decorator is a function that wraps another function to add behavior without changing its source code.
- '@decorator' above a function definition is shorthand for 'func = decorator(func)'.
- The inner wrapper typically uses *args and **kwargs to accept any arguments and forward them to the original function.
- functools.wraps(func) preserves the original function's __name__, __doc__, and other metadata on the wrapper.
- Decorators are commonly used for logging, timing, caching, authentication, and validation.
Practice what you learned
1. What does the syntax '@my_decorator' above a function definition actually do?
2. Why do wrapper functions inside decorators typically use *args and **kwargs?
3. What problem does functools.wraps solve?
4. What happens to add.__name__ if @functools.wraps(func) is NOT used in the wrapper?
5. Which of the following is a typical use case for decorators?
Was this page helpful?
You May Also Like
Lambda Functions in Python
Writing small anonymous functions with lambda, using them with map/filter/sorted, and avoiding the late-binding closure trap in loops.
Functions in Python
How to define, call, and document reusable blocks of code with Python functions, including the def keyword and implicit None return.
Generators in Python
Learn how to write generator functions with yield to produce lazy, memory-efficient sequences of values in Python.
with Statement (Context Managers) in Python
Learn how the 'with' statement uses the context manager protocol (__enter__/__exit__) to guarantee cleanup, such as automatically closing files.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics