Python

How to Enhance Your Python Scripts with Decorators

Python decorators are a strong tool to enhance the Python programs in various ways. Decorators enable the developers to change the behaviour of functions and classes without changing the underlying code. This can improve the code reusability, maintainability, and readability. It is a means of adding a functionality to the functions or methods without directly altering their code. Using the decorators, we may make the code more reusable, maintainable, understandable, and performant.

Understand the Basics of Decorators

  • A Python function known as a decorator accepts a new function (or method) as an argument.
  • It often yields a new function and extends the functionality of the input function.
  • Decorators are used in conjunction with the target function definitions by utilizing the @decorator_name syntax.

Tips to Use the Decorators Effectively

  • Use the decorators to add a functionality to your code that is not specific to any function or class. This makes the code more reusable.
  • Be careful not to overuse the decorators as this can make your code difficult to read and understand.
  • Use descriptive names for decorators so that it is clear what they do.
  • Document the decorators thoroughly so that the other developers can use them effectively.

Example 1: Logging

Decorators can be used to log the execution time and return the value of functions automatically. Both performance monitoring and troubleshooting may benefit from this. The following decorator, for instance, might be used to record the duration of any function’s execution:

Code Snippet:

import time as tm
def log_execution_time(func_input_name):
    def wrapper(*args, **kwargs):
        execution_start_time = tm.time()
        excution_time_consume = func_input_name(*args, **kwargs)
        execution_end_time = tm.time()
        function_execution_time = execution_end_time - execution_start_time
        print(f"Function {func_input_name.__name__} took {function_execution_time:.3f} seconds to execute.")
        return excution_time_consume
    return wrapper    
@log_execution_time
def my_dec_function():
    print("Using the Decorator Function")
    pass
my_dec_function()

Code Explanation:

A wrapper function is returned by this decorator function which accepts a function name as an input. The parameters that the original function receives and returns are also accepted by the wrapper function. Nevertheless, the wrapper function also records the original function’s execution time to the console. Simply apply the log_execution_time() decorator to the function for which execution time you want to log. For example, in the code that you provided, the my_dec_function() function is decorated using the log_execution_time() decorator. This means that when the log_execution_time() decorator is used, the execution time for the my_dec_function() function is logged to the console.

The output of the given code is seen in the subsequent screenshot:

A black screen with white text Description automatically generated

Example 2: Add an Item to the Cart

Here is an illustration of a decorator that may be used to ascertain a user’s authorization to add a new item to the cart. To restrict an access to the add_item_to_cart function, we can use the authorize_add_item_to_cart decorator. Here’s how we’d put it to use:

First, ensure that we define the authorize_add_item_to_cart decorator as specified.

To prevent an unauthorized access to the add_item_to_cart function, let’s use the authorize_add_item_to_cart decorator. Authorize_add_item_to_cart() is a decorator function that accepts an input function and produces an output wrapper function. The wrapper function accepts and returns the same parameters as the original function. Contrarily, the wrapper function checks to see if the user has permission to call the underlying function. The wrapper function throws a PermissionError exception if the user is not permitted.

The authorize_add_item_to_cart() decorator is applied to the add_item_to_cart() function. This means that when the authorize_add_item_to_cart() decorator is invoked, it will first check if the user can call the add_item_to_cart() function. If the user is not permitted, the decorator throws a PermissionError exception. If this is not the case, the decorator uses the original add_item_to_cart() function.

The following code demonstrates how to restrict an access to the add_item_to_cart() function using the authorize_add_item_to_cart() decorator:

Code Snippet:

def is_authorized_to_add_item_to_cart():
    # Implement your authorization logic here
    # Return True if authorized, False otherwise
    return True  # For demonstration purposes, always returning True
# Define the decorator
def authorize_add_item_to_cart(input_func):
    def wrapper(*args, **kwargs):
        if not is_authorized_to_add_item_to_cart():
            raise PermissionError("You are not authorized to add items to the cart.")
        return input_func(*args, **kwargs)
    return wrapper
# Use the decorator to restrict access to the add_item_to_cart function
@authorize_add_item_to_cart
def add_item_to_cart(product_id, pquantity):
    # This function can only be called if authorized
    # Add the item to the cart logic here
    print(f"Added {pquantity} of product {product_id} to the cart")
# Now, when you call add_item_to_cart, it will check for authorization
# before executing the actual logic.
try:
    add_item_to_cart(123, 2)  # This should work since is_authorized_to_add_item_to_cart returns True
except PermissionError as e:
    print(e)
# If is_authorized_to_add_item_to_cart returns False, it will raise a PermissionError:
try:
    add_item_to_cart(456, 3)  # This will raise PermissionError
except PermissionError as e:
    print(e)

Output:

Multiple Decorators

Here is a unique and real-world example of using multiple decorators with a scenario:

Scenario:

We’re working on a web application that allows people to post and comment on articles. We intend to include the following features:

• Only logged-in users could post and comment on articles.

• Before the other users can see your comments, they must be authorized by a moderator.

• All article posts and comments must be logged so that you can measure the website activity.

Solution:

We can use multiple Python decorators to implement these features. Here is an example:

Code Snippet:

from functools import wraps
def is_logged_in():
    return True
def is_moderator():
    return True
def is_comment_approved(inputcomment):
    return True
def authenticate_user(inputfunc):
    @wraps(inputfunc)
    def wrapper(*args, **kwargs):
        if not is_logged_in():
            raise PermissionError("You are not logged in.")
        return inputfunc(*args, **kwargs)
    return wrapper
def require_moderation(inputfunc):
    @wraps(inputfunc)
    def wrapper(*args, **kwargs):
        inputcomment = args[0]
        if not is_moderator():
            raise PermissionError("You are not a moderator.")
        if not is_comment_approved(inputcomment):
            raise PermissionError("Comment is not approved.")
        return inputfunc(*args, **kwargs)
    return wrapper
def log_func_activity(inputfunc):
    @wraps(inputfunc)
    def wrapper(*args, **kwargs):
        log_entry = f"{inputfunc.__name__} called with args: {args} and kwargs: {kwargs}"
        print(log_entry)
        return inputfunc(*args, **kwargs)
    return wrapper
@authenticate_user
@require_moderation
@log_func_activity
def post_comment(inputcomment):
    # Save the comment to the database
    pass
@authenticate_user
@log_func_activity
def post_article(article):
    # Save the article to the database
    pass
post_comment("This is my comment.")
post_article("This is my article.")

Complete explanation of the code that is written in Python for decorators:

The four decorators are:

  • authenticate_user(): This decorator checks if the user is logged in before calling the decorated function. If the user is not logged in, the decorator raises a PermissionError exception.
  • require_moderation(): This decorator checks if the user is a moderator and that the comment is approved before calling the decorated function. If the user is not a moderator or the comment is not approved, the decorator raises a PermissionError exception.
  • log_func_activity(): This decorator logs the call to the decorated function in the console.
  • wraps(): The original function’s metadata are propagated to the decorated function by this decorator. This is important so the decorated function still has the same name, documentation, and other properties as the original function.

The two functions are:

  • post_comment(): This function saves a comment to the database.
  • post_article(): This function saves an article to the database.

The post_comment() and post_article() functions are decorated with the authenticate_user(), require_moderation(), and log_func_activity() decorators. This means that when these functions are called, the following happens:

  1. The authenticate_user() decorator checks if the user is logged in. If the user is not logged in, the decorator raises a PermissionError exception.
  2. The require_moderation() decorator checks if the user is a moderator and that the comment is approved (for the post_comment() function only). If the user is not a moderator or the comment is not authorized, the decorator raises a PermissionError exception.
  3. The log_func_activity() decorator logs the call to the decorated function in the console.
  4. Finally, the decorated function (either post_comment() or post_article()) is called.

Here is an example of the output of the code when the post_comment() function is called:

Conclusion

Decorators in Python are potent tools to dynamically change the function behaviors which are used for tasks like authentication, logging, and access control. They can be stacked to apply multiple decorators to a single function, but the proper usage and attribution are crucial to avoid plagiarism and respect the intellectual property rights.

About the author

Kalsoom Bibi

Hello, I am a freelance writer and usually write for Linux and other technology related content