What is a Python Decorator?

Python decorators allow modifying the behavior of functions and classes, without changing their code. Let’s start from a simple example and see where a decorator would fit.

Here is a simple function to play with:

def myfunc():
	print("in myfunc")

Let’s assume we want to add a logging mechanism for functions like myfunc that adds a print before the function is called. We can use a wrapper function that prints before calling the function, which is straightforward in Python since functions are first-class objects and can be passed to other functions:

def print_wrapper(func):
    def wrapper():
        print(f“calling {func.__name__}”)
        func()
    return wrapper
myfunc = print_wrapper(myfunc)
>> myfunc()
calling myfunc
in myfunc

Essentially, we are replacing myfunc with another function that has more functionality. To make this process simpler, Python provides the decorator syntax for these cases:

@print_wrapper
def myfunc2():
	print(“in myfunc2”)
>> myfunc2()
calling myfunc2
in myfunc2

The @print_wrapper decorator adds the wrapper for the myfunc2 function without explicit calls to print_wrapper as we did for myfunc.

Bodo provides the jit decorator that replaces data analytics functions (e.g. using Pandas) with an optimized and parallelized binary version automatically through just in time (JIT) compilation. This is as if the function is rewritten to parallel C++ by an expert, but happens transparently in real-time. For example:

@bodo.jit
def my_pandas_func():
    ...
>> my_pandas_func
CPUDispatcher()

In this case, CPUDispatcher is the name of the wrapper that manages compilation and calls to the optimized version of my_pandas_func.