Race Conditions

One commit at a time

Notes About Decorators

Decorators are one language feature in Python that I frequently use as a consumer of libraries and frameworks such as Flask but rarely implement myself. It’s time to revisit this feature! Thank you very much to Mark Lutz’s discussion in the 5th Edition of Learning Python. All mistakes are my own and feedback and comments are always appreciated.

Decorators are a way to augment Python functions and classes. They do this by providing a syntax that allows us to associate code that should be run before/after a call to a function or a class. Let’s take a look at an example. Suppose we want to print ‘hello world’ before every call to then function hello. Functions are first class objects in Python, which means that you can pass them in as arguments to other functions. Let’s take advantage of this feature to develop a helper function to achieve our goal.

def helperfunc(dec_func):
    """
    Accepts the function to be decorated `dec_func`
    and returns a wrapper function
    """
    def wrapper(*args):
        print('hello world')
        dec_func(*args)
    return wrapper

The function we want to wrap is below:

def hello():
    print('Hello, how are you?')
    

It simply prints some characters :). Now, if we want to augment the function hello, we can pass it into the helper function helper_func. As you can see from the code above, helper_func returns another function - wrapper that will call the function we pass into helper_func as an argument and will also print ‘hello world’.

decorated_func = helperfunc(hello)

We now have a new function! decorated_func will call our function hello, but will also print out the extra hello world!

This is all well and great, but suppose we have some downstream client libraries that are expecting a function called hello. Because of the way namebinding works in Python, we can simply bind the new decorated function decorated_func to the variable hello.

hello = helperfunc(hello)

This is the result when we call hello:

>>> hello()
hello world
Hello, how are you?

This is cool! But writing

hello = helperfunc(hello)

around all of the functions we wish to decorate would be tedious. To help us avoid this tedium, we have the decorator syntax. We simply decorate the function hello like this

@helperfunc
def hello():
    print('Hello, how are you?')
    

And voila - we’ve just written our first decorator!

We should also note that this would work also in the case where the decorated function accepts arguments.

@helperfunc
def hello_name(name):
    print('Hello, ', name)
>>> hello_name('Charlene')
hello world
Hello, how are you  Charlene