In the post “Python decorators for dummies (me)” I’ve explained something about the python decorators. In the post there are theorical examples, in this post I want to show some real world use cases for the python decorators.

The very first use case is a timing decorator. I use it to compute the execution time of a function, for debugging and profiling purpose. It is a very simple decorator but it is also very useful. Before and after the execution of the decorated function, the decorator takes the time and then before returning, it prints out the execution time.

from functools import wraps
import time

def time_fun(method):
    @wraps(method)
    def wrap_timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time() - ts
        print(method.__name__ + " " + str(te*1000))
        return result
    return wrap_timed

@time_fun
def do_things():
    '''
    @brief a function that prints hello world
    '''
    print('Hello World')

if __name__ == '__main__':
    do_things()

the output is as follows:

Hello World
do_things 0.6868839263916016

Another decorator I often use is the log decorator, it helps me debugging my apps.

def log(logger, level='info'):
    def log_decorator(fn):
        @wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

to use the log decorator you have to instantiate your logger

LOGGER = logging.getLogger('__main__')
LOGGER.setLevel(logging.DEBUG)
F_HANDLER = logging.FileHandler('log_file.log')
F_HANDLER.setLevel(logging.DEBUG)
LOGGER.addHandler(F_HANDLER)
FORMATTER = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
F_HANDLER.setFormatter(FORMATTER)

and then you have to apply the decorator to your functions

@log(LOGGER, level='debug')
def do_things(tvalue):
    time.sleep(tvalue)

if __name__ == '__main__':
    do_things(0.8)

After executing the script, you will have a file named ‘log_file.log’ with the following content:

2017-12-12 09:16:52,870 - __main__ - DEBUG - do_things

Last interesting use case is the use of decorators as synchronizations for threads.
The decorator is really simple. It executes the decorated function only after it takes the lock, when the decorator function returns it releases the lock.

LOCK = threading.Lock()
def synchro(lock_id):
    def wrap(method):
        def wrapped_function(*args, **kw):
            with lock_id:
                return method(*args, **kw)
        return wrapped_function
    return wrap

@synchro(LOCK)
def worker1(name, timing):
    for _ in range(5):
        time.sleep(timing)
        print(name)

def worker(name, timing):
    for _ in range(5):
        time.sleep(timing)
        print(name)

if __name__ == '__main__':
    print('Not synchronized threads')
    TH1 = threading.Thread(target=worker, args=('TH1', 0.1,))
    TH2 = threading.Thread(target=worker, args=('TH2', 0.2,))
    TH1.start()
    TH2.start()
    TH1.join()
    TH2.join()
    print('Synchronized threads')
    STH1 = threading.Thread(target=worker1, args=('STH1', 0.1,))
    STH2 = threading.Thread(target=worker1, args=('STH2', 0.5,))
    STH1.start()
    STH2.start()

the execution of this script will output:

Not synchronized threads
TH1
TH1
TH2
TH1
TH1
TH2
TH1
TH2
TH2
TH2
Synchronized threads
STH1
STH1
STH1
STH1
STH1
STH2
STH2
STH2
STH2
STH2

Non-synchronized threads evolve in parallel while synchronized threads execute one after the other.

And what are your favourite decorators?