Python decorators for fun and profit

Two python functions:

1
2
3
4
5
6
def print_status_and_execute(f):
    print("running function " + f.__qualname__)
    return f

def add(x, y):
    return x+y

You might want to wrap them like:

1
status_printing_add = print_status_and_execute(add)

Which is kind of ok, except that now you have to change all your code wherever you used to use add to use the new status_printing_add function.

Instead, you can just decorate the function definition of add like so:

1
2
3
4
5
6
7
8
9
def print_status_and_execute(f):
    def fn(*args, **kwargs):
        print("running function " + f.__qualname__)
        return f(*args, **kwargs)
    return fn

@print_status_and_execute
def add(x, y):
    return x+y

And now every instance of add behaves as if it is automatically transformed: add(a, b) -> print_status_and_execute(add)(a, b)

You can even make decorators themselves depend on arguments, though it requires an extra level of abstraction:

1
2
3
4
5
6
7
8
9
10
11
12
13
def print_n_times(n):
    def decorator(f):
        def fn(*args, **kwargs):
            for i in range(n):
                print(*args)
                print(**kwargs)
            return f(*args, **kwargs)
        return fn
    return decorator

@print_n_times(5)
def say_hello(name):
    print("hello " + name)

Or do fancier things, like create and maintain extra data structures available to their context across function calls:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def memoize(fn):
    prev_res = {}
    def mem(num):
        if num in prev_res.keys():
            return prev_res[num]
        else:
            val = fn(num)
            prev_res[num] = val
            return val
    return mem

@memoize
def fibb(n):
    if n == 0:
        return 1
    elif n == 1:
        return 1
    else:
        return fibb(n-1) + fibb(n-2)