In Python, a decorator allows us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it. Decorators provide a simple syntax for calling higher-order functions.
In this post, I will provide some exercises and examples that illustrate the usage of decorators in Python. Let’s dive right in.
1. Basic decorator
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
say_hello()
Output:
2. Using the @ symbol for decorators
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
3. Decorating functions with arguments
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
func(*args, **kwargs)
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def greet(name):
print(f"Hello {name}!")
greet("Pythonista Planet")
Output:
4. Returning values from decorated functions
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def add(x, y):
return x + y
result = add(5, 10)
print(result)
Output:
5. Using multiple decorators on a single function
def decorator_1(func):
def wrapper():
print("Decorator 1")
func()
return wrapper
def decorator_2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper
@decorator_1
@decorator_2
def greet():
print("Hello!")
greet()
Output:
6. Class decorators
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self):
print("Something is happening before the function is called.")
self.func()
print("Something is happening after the function is called.")
@MyDecorator
def greet():
print("Hello!")
greet()
Output:
7. Decorators with arguments
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}")
greet("Pythonista Planet")
Output:
8. Caching return values
import functools
@functools.lru_cache(maxsize=4)
def fibonacci(num):
print(f"Calculating fibonacci({num})")
if num < 2:
return num
return fibonacci(num - 1) + fibonacci(num - 2)
print(fibonacci(10))
Output:
9. Making a decorator with user-defined exception
def validate(func):
def wrapper(*args):
if len(args) > 0 and isinstance(args[0], int):
if args[0] > 0:
return func(*args)
raise ValueError("Value must be a positive integer")
return wrapper
@validate
def print_num(num):
print(num)
print_num(10)
print_num(-1)
Output:
I hope that these examples would give you a good understanding of decorators in Python. Decorators can be a powerful tool, and while they might seem a bit confusing at first, with some practice, you’ll be able to use them effectively in your code.
Remember that they are simply functions that take a function and return a new function. This concept of functions returning functions is one of the cornerstones of functional programming and is part of what makes Python such a powerful and flexible language.
If you found this post useful, check out my post on 20 Python File I/O Exercises and Examples.