Decorators in Python are a powerful and flexible tool that allows you to extend or modify the behavior of functions and methods. With decorators, you can wrap additional functionality around functions, making your code more modular, readable, and reusable. This tutorial covers everything you need to know about decorators, including syntax, practical examples, and best practices.
A decorator in Python is a function that takes another function (or method) and extends its behavior without modifying its structure. Decorators are commonly used in Python for logging, access control, instrumentation, and memoization. They are created using the @decorator_name
syntax, making it easy to add functionality to functions in a readable and reusable way.
Decorators offer several benefits:
A decorator function takes another function as its argument, and it usually returns a wrapper function that extends the behavior of the original function.
def decorator_name(func):
def wrapper(*args, **kwargs):
# Add code to execute before the function call
result = func(*args, **kwargs)
# Add code to execute after the function call
return result
return wrapper
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()
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
Decorators use closures (functions inside functions) to extend functionality. When a function is decorated, the decorator modifies it by wrapping the function with additional code defined in the wrapper.
@my_decorator
is syntactic sugar for say_hello = my_decorator(say_hello)
.say_hello
in the wrapper function, which adds behavior before and after the original function call.Let’s start with a simple decorator that prints messages before and after a function runs.
def simple_decorator(func):
def wrapper():
print("Function is about to run.")
func()
print("Function has finished running.")
return wrapper
@simple_decorator
def greet():
print("Hello, World!")
greet()
Function is about to run.
Hello, World!
Function has finished running.
Decorators can handle arguments by using *args
and **kwargs
in the wrapper function. This allows the decorator to wrap functions of varying arguments.
def debug_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with arguments {args} and {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@debug_decorator
def add(a, b):
return a + b
add(5, 3)
Calling add with arguments (5, 3) and {}
add returned 8
*args
and **kwargs
allow the decorator to work with functions that take any number and type of arguments.You can apply multiple decorators to a single function. Decorators are applied from top to bottom.
def decorator_one(func):
def wrapper():
print("Decorator One")
func()
return wrapper
def decorator_two(func):
def wrapper():
print("Decorator Two")
func()
return wrapper
@decorator_one
@decorator_two
def greet():
print("Hello!")
greet()
Decorator One
Decorator Two
Hello!
Decorators can also be implemented using classes. Class-based decorators use the __call__
method to make instances of the class callable like functions.
class DecoratorClass:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Class-based decorator: Before the function call")
result = self.func(*args, **kwargs)
print("Class-based decorator: After the function call")
return result
@DecoratorClass
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Class-based decorator: Before the function call
Hello, Alice!
Class-based decorator: After the function call
__call__
method allows instances of DecoratorClass
to be used as a decorator, enabling them to wrap functions.Decorators can be used to log function calls, recording when they are executed and with what arguments.
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Logging: {func.__name__} called with {args} and {kwargs}")
return func(*args, **kwargs)
return wrapper
@log_decorator
def multiply(a, b):
return a * b
multiply(3, 4)
Logging: multiply called with (3, 4) and {}
Timing a function’s execution can help optimize performance.
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time} seconds to execute")
return result
return wrapper
@timer_decorator
def compute_sum(limit):
return sum(range(limit))
compute_sum(1000000)
compute_sum took 0.03 seconds to execute
Decorators can enforce access control, such as requiring a user to be logged in.
def require_authentication(func):
def wrapper(user):
if not user["is_authenticated"]:
print("User is not authenticated.")
return
return func(user)
return wrapper
@require_authentication
def view_profile(user):
print(f"Profile: {user['name']}")
user = {"name": "Alice", "is_authenticated": True}
view_profile(user) # Output: Profile: Alice
unauthenticated_user = {"name": "Bob", "is_authenticated": False}
view_profile(unauthenticated_user) # Output: User is not authenticated.
require_authentication
decorator checks the is_authenticated
status of the user before allowing access to view_profile.@log_decorator
or @timer_decorator
.@decorator_name
syntax and are implemented with wrapper functions.__call__
method to create decorators that need state or complex behavior.Decorators in Python are an invaluable tool for adding functionality to functions and methods. By wrapping functions, decorators allow you to add behavior like logging, timing, access control, and input validation without altering the original function’s code. Whether you need a simple logging decorator or a complex class-based decorator with state, decorators help you write more modular, reusable, and readable code. Following best practices ensures that decorators enhance rather than complicate your code.
With Python decorators, you can:
Ready to enhance your Python functions with decorators? Practice creating decorators for common tasks like logging and access control to master this versatile tool. Happy coding!