ELI5 Tutorial: Decorators in Python

The next stop on our ELI5 tutorials is going to be decorates. These are another example of a very powerful bit of code that can be hard to understand. The quick description would be that these are ways to add code that runs before and after a function call. Read on for a more in-depth explanation.

Our objective for this is going to make code for messaging users about when a function starts and stops. This is a helpful feature to include in your code, especially if there are functions with a long execution time, as it would let users know what part of the script is currently running. Here are two simple functions to start:

def adder(a, b):
    print("starting adder")
    output = a + b
    print("finished adder") 
    return output

def multiplier(a, b):
    print("starting multiplier")
    output = a * b
    print("finished multiplier")
    return output

This code does the job fine, but there’s some repetition. As a general rule, you should avoid writing the same code multiple times as it can make the script difficult to read. In addition, it’d be nice if we could easily enable/disable the messaging which currently involves changing 2 lines of code per function. So, first lets define our decorator, which we will call “messenger”:

def messenger(function):
    def wrapper(*args, **kwargs):
        functionName = function.__name__
        print(f"starting {functionName}")
        output = function(*args, **kwargs)
        print(f"ending {functionName}")
        return output
    return wrapper

Wow, that looks pretty weird, right? This is an ELI5 tutorial so I won’t focus much on why we do it like this, the simplest answer is that this is simply how decorators work.

Line by line, here is what is going on:
First, we define our decorator named “messenger” as a a function that takes another function as its argument.
Then, inside the decorator we define a “wrapper” function that takes *args and **kwargs. We need to use *args and **kwargs here in order to allow any possible function to be decorated.
The function.__name__ I use on the next line returns the name of the function as a string.
In the print statements I am using f strings for extra readability, the {functionName} part will be replaced by the string held by the functionName variable. This is only valid for python 3.6+
Then we get the output of the function by calling it with *args and **kwargs.
Finally, the wrapper function will return the output, then the messenger function returns the wrapper.

So, how do we use this? It is pretty simple, we just write @messenger on the line above the function definitions. The final code will look like so:

def messenger(function):
    def wrapper(*args, **kwargs):
        functionName = function.__name__
        print(f"starting {functionName}")
        output = function(*args, **kwargs)
        print(f"ending {functionName}")
        return output
    return wrapper

@messenger
def adder(a, b):
    output = a + b
    return output

@messenger
def multiplier(a, b):
    output = a * b
    return output

Now, when we call adder and multiplier the @messenger decorator will print the “starting function” and “ending function” lines before and after it runs the function. While defining the decorator itself can be a bit tricky, once defined it is simple to add or remove it from your functions.

If you want to practice, I would suggest turning the timer function from the end of the *args and **kwargs tutorial into a decorator that prints execution times. This can be a very useful decorator to have as it can allow you to quickly add checking for slow function execution on a complex script.

I hope I was able to help you understand decorators a little better. Our next EL15 tutorial will be an introduction to objects in python.

This entry was posted in ELI5, Python, tutorial and tagged , . Bookmark the permalink.

Leave a Reply

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