Python Decorators
In this page:
def decorator_name(func):
def wrapper(*args, **kwargs):
# before
result = func(*args, **kwargs)
# after
return result
return wrapper
@decorator_name
def function_name():
pass
What is a Decorator?
A decorator is a function that wraps another function, adding behavior before or after the original runs, without permanently modifying the original function's own code -- it takes a function in and returns a (usually new, wrapped) function out.
Example: What is a Decorator?
def shout(func):
def wrapper():
print(func().upper()) # extra behavior added around the original call
return wrapper
@shout
def greet():
return "hello"
greet() # actually calls wrapper(), not the original greet
Designing a Basic Decorator
Applying a decorator manually just means calling it with your target function and using whatever it returns: 'wrapped = my_decorator(original_function)' -- understanding this manual form is what makes the @ shorthand's actual behavior clear rather than feeling like magic.
Example: Designing a Basic Decorator
def shout(func):
def wrapper():
print(func().upper())
return wrapper
def greet():
return "hello"
wrapped = shout(greet) # manually applying the decorator, same as @shout
wrapped()
Decorating with Arguments
Because the function being decorated might accept any combination of arguments, a general-purpose wrapper collects them with *args and **kwargs and forwards them unchanged to the original function -- without this, a decorator would only work on functions with one exact, fixed signature.
Example: Decorating with Arguments
def log(func):
def wrapper(*args, **kwargs): # accepts any arguments the original function takes
print("Calling", func.__name__)
return func(*args, **kwargs) # forwards them unchanged
return wrapper
@log
def add(a, b):
return a + b
print(add(2, 3))
The @ Syntax
Writing '@my_decorator' directly above a function definition is exactly equivalent to writing 'my_function = my_decorator(my_function)' right after it -- the @ syntax is pure convenience, not a different mechanism underneath.
Example: The @ Syntax
def shout(func):
def wrapper():
return func().upper()
return wrapper
@shout
def greet():
return "hi"
print(greet()) # same as greet = shout(greet)
Multiple Decorators
Stacking multiple decorators on one function applies them from the bottom up -- the decorator closest to the function wraps it first, and each decorator above that wraps the result of the one below it, so execution order matters when the decorators interact.
Example: Multiple Decorators
def bold(func):
def wrapper():
return "**" + func() + "**"
return wrapper
def shout(func):
def wrapper():
return func().upper()
return wrapper
@bold
@shout # applied first, closest to the function
def greet():
return "hi"
print(greet())
- Forgetting to
returnthe inner wrapper function from the decorator, so the decorated name becomesNone. - Not passing
*args, **kwargsthrough the wrapper, so the decorator breaks functions with arguments. - Forgetting
functools.wraps, so the decorated function loses its__name__and docstring.
Chapter Quiz — Complete all 12 topics to unlock
0/12 topics done
Complete these topics first: