Power Up Your Python Code Using Decorators

Content:

Python is known for its simplicity and readability, which makes it a favourite among both beginners and experienced developers. Among its many powerful features, which stand compared to other languages, are decorators.

A decorator in Python is a design pattern that allows you to modify the behaviour of a function or method. They are functions that wrap other functions, allowing you to add functionality before or after the target function runs, without changing its code.

This post will explain how you can use decorators to enhance your Python code.

Defining a Decorator

To define a decorator, you need to create a function that takes another function as an argument and returns a new function that adds some kind of enhancement to the original function.

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()

When you run this code, the output will be:

In this example, the function my_decorator takes another function as its parameter, where it can then be called from within the function.

The output from this function will look like this:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

This illustrates the concept of a decorator. The my_decorator function is adding additional code before and after the passed function runs, without the passed function needing to be modified directly.

Using the @ Syntax

Python provides a more readable and concise way to apply decorators using the @ symbol. This is especially useful when you want to apply multiple decorators to a function. Here’s how you can rewrite the previous example using the @ syntax:

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()

By adding the decorator to the function, we can now all it directly, without having to pass it to the decorator function. This makes the code cleaner, and easier to understand.

Practical Applications of Decorators

Decorators are incredibly useful for a variety of tasks in real-world applications. Here are a few common use cases:

  • Adding logging to selected functions for debugging
  • Checking for user authorisation before an action is performed
  • Storing and caching results and returning them without re-executing the function
  • Validating data before executing the function, or checking return data after the function has executed

Logging is a particularly powerful use of decorators, and can be used to add both debug logging and release logging in a flexible manner.

def log_function(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function '{func.__name__}' with arguments {args} and {kwargs}")
        result = func(*args, **kwargs)
        print(f"'{func.__name__}' returned {result}")
        return result
    return wrapper

@log_function
def add(a, b):
    return a + b

In this example, the return value of the call add(2, 3) will be:

Calling function 'add' with arguments (2, 3) and {}
'add' returned 5

Conclusion

Decorators are a powerful feature in Python that can help you write cleaner, more maintainable code by separating concerns and enhancing functionality in a reusable way. Be sure to try it out in your projects, to make your Python code cleaner.

If you like what we do, consider supporting us on Ko-fi