Python Decorators
Python decorators are a powerful tool for modifying or enhancing the behavior of functions or methods. In this guide, we will go through the fundamental concepts and provide examples of decorators in Python.
What Are Python Decorators?
A Python decorator is a function that modifies or enhances the behavior of another function. Decorators allow you to add functionality to an existing function without modifying its code. They are used to wrap functions with additional behavior.
Decorators are commonly used in situations where we need to apply the same logic across multiple functions (such as logging, validation, or authentication).
How Do Decorators Work?
A decorator works by taking a function as an argument, modifying it, and then returning a new function that adds functionality to the original one. This allows you to enhance the behavior of the function without changing its code.
Here’s an example that shows how decorators work:
def my_decorator(func):
def wrapper():
print("Before the function runs.")
func()
print("After the function runs.")
return wrapper
def greet():
print("Hello, World!")
greet = my_decorator(greet)
greet()
def my_decorator(func):
def wrapper():
print("Before the function runs.")
func()
print("After the function runs.")
return wrapper
def greet():
print("Hello, World!")
greet = my_decorator(greet)
greet()
How It Works:
- my_decorator(func) defines a decorator function that takes another function as its argument.
- wrapper() adds behavior before and after calling func().
- greet = my_decorator(greet) applies the decorator to the greet function.
- greet() now runs the decorated version, printing messages before and after the original greeting.
Output:
Before the function runs.
Hello, World!
After the function runs.
Before the function runs.
Hello, World!
After the function runs.
Basic Structure of a Decorator
A decorator consists of two main components:
- The decorator function itself, which accepts another function as a parameter.
- The wrapper function that modifies or enhances the behavior of the original function.
Here’s the basic structure of a decorator:
def decorator_name(func):
def wrapper():
# Add functionality here
return func()
return wrapper
def decorator_name(func):
def wrapper():
# Add functionality here
return func()
return wrapper
How Does the @ Symbol Work?
The @ symbol** is shorthand for applying a decorator to a function. Instead of manually passing the function through the decorator, you can use the `@decorator_name` syntax to apply the decorator directly above the function definition.
This is equivalent to writing:
greet = my_decorator(greet)
greet = my_decorator(greet)
But with the `@` symbol, it’s cleaner and easier to read:
@my_decorator
def greet():
print("Hello, World!")
@my_decorator
def greet():
print("Hello, World!")
A Decorator with Arguments
If the function being decorated accepts arguments, the decorator must also be able to accept and pass those arguments to the original function.
Here’s an example where the decorator accepts and passes arguments to the decorated function:
def decorator_with_args(func):
def wrapper(*args, **kwargs):
print("Arguments received:")
print("Positional:", args)
print("Keyword:", kwargs)
return func(*args, **kwargs)
return wrapper
@decorator_with_args
def greet(name, age):
print(f"Hello {name}, you are {age} years old!")
greet("Jack", 30)
def decorator_with_args(func):
def wrapper(*args, **kwargs):
print("Arguments received:")
print("Positional:", args)
print("Keyword:", kwargs)
return func(*args, **kwargs)
return wrapper
@decorator_with_args
def greet(name, age):
print(f"Hello {name}, you are {age} years old!")
greet("Jack", 30)
How It Works:
- decorator_with_args(func) defines a decorator that accepts arguments and passes them to the original function.
- wrapper(*args, **kwargs) handles the arguments and prints them out before calling the decorated function.
- greet("Jack", 30) calls the decorated function, showing how the decorator captures and displays the arguments.
Output:
Arguments received:
Positional: ('Jack', 30)
Keyword: {}
Hello Jack, you are 30 years old!
Arguments received:
Positional: ('Jack', 30)
Keyword: {}
Hello Jack, you are 30 years old!
Returning Values from Decorated Functions
Decorators can also modify or manipulate the return value of the function they decorate. The wrapper function can return a value, just like the original function.
Here’s an example where the decorator modifies the return value:
def return_value_decorator(func):
def wrapper():
result = func()
return f"Modified: {result}"
return wrapper
@return_value_decorator
def greet():
return "Hello, World!"
print(greet())
def return_value_decorator(func):
def wrapper():
result = func()
return f"Modified: {result}"
return wrapper
@return_value_decorator
def greet():
return "Hello, World!"
print(greet())
How It Works:
- return_value_decorator(func) defines a decorator that modifies the return value of the original function.
- wrapper() calls the decorated function, captures its result, and modifies it before returning.
- @return_value_decorator applies the decorator to the greet() function.
- greet() now returns the modified string instead of the original one.
Output:
Modified: Hello, World!
Modified: Hello, World!
Chaining Multiple Decorators
You can apply multiple decorators to a function. They will be executed in the order they are listed, with the decorator closest to the function being applied first.
Here’s an example of chaining multiple decorators:
def decorator_one(func):
def wrapper():
print("Decorator One")
return func()
return wrapper
def decorator_two(func):
def wrapper():
print("Decorator Two")
return func()
return wrapper
@decorator_one
@decorator_two
def greet():
print("Hello, World!")
greet()
def decorator_one(func):
def wrapper():
print("Decorator One")
return func()
return wrapper
def decorator_two(func):
def wrapper():
print("Decorator Two")
return func()
return wrapper
@decorator_one
@decorator_two
def greet():
print("Hello, World!")
greet()
How It Works:
- decorator_one(func) and decorator_two(func) are two decorators that modify the behavior of the greet function.
- Decorators are applied in a bottom-up manner, so decorator_two is applied first, followed by decorator_one.
- The greet function is decorated by both, and each decorator prints a message before executing the function.
Output:
Decorator One
Decorator Two
Hello, World!
Decorator One
Decorator Two
Hello, World!