Python decorators are a powerful tool for extending the functionality of existing code. They are essentially functions that modify the behavior of other functions or classes. In this blog post, we will explore what decorators are, how to use them, and provide some examples of how decorators can be used to add additional functionality to Python code.
What are Decorators?
In Python, a decorator is a special type of function that takes another function as its argument and returns a new function that incorporates the behavior of the original function. In other words, decorators allow you to modify the behavior of a function without changing its code.
Here's a simple example of a decorator function:
def my_decorator(func):
def wrapper():
print("Before function is called.")
func()
print("After function is called.")
return wrapper
In this example, we define a decorator function called my_decorator
that takes another function as its argument (func
). Inside the decorator function, we define a new function called wrapper
that prints a message before and after calling the original function. Finally, the decorator function returns the wrapper
function.
To use the decorator, we simply apply it to another function using the @
symbol:
>>> my_function()
Before function is called.
Hello, world!
After function is called.
This is just a simple example of how decorators work. In practice, decorators can be much more complex and can be used to add a wide variety of functionality to Python code.
Examples of Decorators in Python
Let's explore some more examples of how decorators can be used to add additional functionality to Python code.
- Timing Decorator
One common use of decorators is to add timing functionality to functions. Here's an example of a timing decorator:
import time
def time_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print("Function took", end_time - start_time, "seconds to complete.")
return result
return wrapper
In this example, we define a decorator function called time_decorator
that takes another function as its argument. Inside the decorator function, we define a new function called wrapper
that calculates the start and end times, calls the original function, calculates the total time taken, and prints the result.
To use the timing decorator, we simply apply it to another function using the @
symbol:
@time_decorator
def my_function():
time.sleep(1)
In this example, we apply the time_decorator
function to the my_function
function using the @
symbol. Now, when we call my_function
, the timing decorator will be called first, and it will print the time taken to execute the function:
>>> my_function()
Function took 1.0011999607086182 seconds to complete.
- Authorization Decorator
Another common use of decorators is to add authorization functionality to functions. Here's an example of an authorization decorator:
def authorized(allowed_roles):
def decorator(func):
def wrapper(*args, **kwargs):
user_roles = get_user_roles()
In the example, we define a decorator function called authorized
that takes a list of allowed roles as its argument. Inside the decorator function, we define a new function called decorator
that takes another function as its argument. Inside the decorator function, we define a new function called wrapper
that checks if the user has the appropriate roles to call the original function. If the user has the appropriate roles, the original function is called. If the user does not have the appropriate roles, an error message is returned.
To use the authorization decorator, we simply apply it to another function using the @
symbol:
@authorized(allowed_roles=["admin", "moderator"])
def my_function():
print("Hello, world!")
In this example, we apply the authorized
function to the my_function
function using the @
symbol. Now, when we call my_function
, the authorization decorator will be called first, and it will check if the user has the appropriate roles:
>>> my_function()
Error: User does not have permission to call this function.
In this example, the get_user_roles()
function would need to be defined somewhere in the code to determine the user's roles.
3. Memoization
Decorators can be used to cache the results of a function so that it doesn't have to be recomputed every time it is called. This is known as memoization. Here's an example:
def memoize(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n in (0, 1):
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
In this example, the memoize
decorator caches the results of the fibonacci
function. The wrapper
function inside the decorator checks if the arguments to the function have been cached already. If they have, it returns the cached result. If they haven't, it calls the original function and caches the result.
Conclusion
Python decorators are a powerful tool for extending the functionality of existing code. They allow you to modify the behavior of a function without changing its code. In this blog post, we explored what decorators are, how to use them, and provided some examples of how decorators can be used to add additional functionality to Python code. While decorators can be complex, they are a valuable tool to have in your programming arsenal.