Decorate with decorators

Decorators are to Python what annotations are to Java. They were introduced since Python 2 and are still widely used in order to maintain a clean code and to separate logic which is not strictly related to the behavior of a block of code.

NOTE: decorators in Python are not the same as the decorator design pattern.

In this post, I will talk about Python decorators with the aim of delivering a better understanding of how to write them and how to use them, providing examples as we progress.

Decorators are, simply put, wrappers over existing code. They are used for the purpose of changing or augmenting the behavior of functions and classes. This is where the name comes from, they actually improve code, making it cleaner, thus the word decorate. In this post, we will talk about function decorators.

Function Decorators

Let us take for example the case when we have test methods which are susceptible to throwing out errors like assertions errors or other error that might not be handled in the code. In order to avoid writing duplicated code in each method, we can simply define a decorator that does this and “wrap”  it around our test methods. We would have something like this:


@handle_exception
def test_one():
    do_the_testing()

Now let’s see how we can implement this @handle_exception decorator. The base structure for a decorator is like this:


def decorator(func)
    def wrapper(*args, **kwargs):
        do_something_before_func()
        func(args, kwargs)
        do_something_after_func()
    return wrapper

Now let’s see what we have here:

  • decorator is the actual decorator, which takes as input a parameter called “func” even though when we used it before, we did not specify this parameter. This is because the functions in Python take as first argument the objects it is been called over. In our case a function.
  • func is a reference to the original function. In this way, we have access to the original behavior of the function.
  • wrapper is a nested function which gives us access to the original parameters with which “func” was called. For example, if our initial function would be like this: test_one(p, p1=2), parameter “p” would have been accessed through args and parameter “p1” through kwargs.
  • do_something_before_func is executed before we execute our original function
  • func(args, kwargs) executes the original function together with the original parameters.
  • do_something_after_func is executed after we execute our original function.

Using this, let’s write out handle_exception decorator:


def handle_exception(func)
    def wrapper(*args, **kwargs):
        try:
            func(args, kwargs)
        except Exception as ex:
            print(ex)
    return wrapper

We are simply surrounding our original function with a try-catch mechanism and logging the exception to the standard output.

Now you might wonder what if I want to parametrize a decorator and use it like this:


@handle_exception(log_to_file=True)
def test_one():
    do_the_testing()

In this case, the solution is simple: we just want to have access to additional information in our nested decorator functions. For this, we define another function of the top of the decorator itself:


def handle_exception(log_to_file=False)
    def decorator(func)
        def wrapper(*args, **kwargs):
            try:
                func(args, kwargs)
            except Exception as ex:
                if log_to_file:
                    store_error_to_file(ex)
                print(ex)
        return wrapper
    return decorator

This way we can add an unlimited number of parameters in order to further customize the decorator.

Conclusion

Decorators, in general, let you play with the architecture and the structure of your program. They offer more flexibility to the programmer and provide a different way of modularizing your code leading to better maintainability and extensibility. Function decorators, more specifically allow you to add an extra layer of logic, usually generic like a filer, in order to keep the code, which actually does the important part, clean and readable.

One thought on “Decorate with decorators

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.