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?