Useful Decorators

Last Updated: 10-11-2020

This absolutely excellent talk by James Powell prompted this. It’s entirely worth the watch.

Why Use Decorators

Decorators allow you to quickly extend a function by wrapping it in a another function. This is useful for a number of reasons – perhaps you want to occasionally log details about a function to file, or log the execution time of a function. You can add this code directly, but if you have several functions to add, it’s repetitive and annoying. Doing this with decorators saves you time and effort, and is easier to understand too!

Timer function

Note that their are two commonly used built in decorators, @classmethod and @staticmethod. I won’t really talk about them now, but this is a decent summary.

This is a decorator, called timer. Inside, a wrapper, called f, is defined. Inside f, I start a timer (before = time.perf_counter()), call the function I want to time (rv = func(*args, **kwargs) ), then print the time elapsed ( print(f"Time Elapsed for {func.__name__} is {after - before} seconds") ). That’s it.

The demo code:

import time

def timer(func):
    def f(*args, **kwargs):
        before = time.perf_counter()
        rv = func(*args, **kwargs)
        after = time.perf_counter()
        print(f"Time Elapsed for {func.__name__} is {after - before} seconds")
        return rv
    return f

*args, **kwargs allow me to pass thru arbitrary arguments to func from the wrapper, while func.__name__ accesses the function name using dunder magic.

To use this on a function like

def printHello(myname):
	print(f"Hello {myname}!")

all that is required is

@timer
def printHello(myname):
	print(f"Hello {myname}!")

Now something like

printHello("ethan")

returns

My name is ethan
Time Elapsed for printHello is 0.00038960000000000383 seconds