Decorators

15_decorators

Decorators

  • Function Decorator
  • Class Decorator

Decorators allow us to modify existing classes or functions without redefining them.

Function Decorator

The @wraps decorator helps by copying the meta data of our function so that the decorated version works properly with the help system: help(some_function) will be the same as help(some_decorated_function).

Logger Example

In [0]:
from functools import wraps
from inspect import signature


def logger(func):
    """ Function Decorator for Logging """

    @wraps(func)  # preserves meta data of the func
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        arg_obj = signature(func).bind(*args, **kwargs).arguments.items()
        print(f"LOG: {func.__name__}", end='(')
        print(', '.join(f'{k}={v}' for k, v in arg_obj), end=') ')
        print(f"is {result}")
        return result

    return wrapper

Logger Usage

In [0]:
# Before: No Logging
def mul(x, y):
    """ Multiplies x by y. """
    return x * y

var = mul(2, 3)
In [0]:
# Nothing Printed
In [0]:
# After: Logging Enabled
@logger
def mul(x, y):
    """ Multiplies x by y. """
    return x * y

var1 = mul(2, 3)
var2 = mul(4, y=3)
var3 = mul(x=6, y=5)
LOG: mul(x=2, y=3) is 6
LOG: mul(x=4, y=3) is 12
LOG: mul(x=6, y=5) is 30

Help with @wraps decorator.

help(mul)
Help on function mul in module __main__:

mul(x, y)
    Multiplies x by y.

Help without @wraps decorator.

help(mul)
Help on function wrapper in module __main__:

wrapper(*args)
    # @wraps(func)

Function Decorator Class

Function Cache: Memoization Example

In [0]:
import pickle


class Cache:
    """ Function Cache """

    def __init__(self, function):
        self.cache = {}
        self.function = function
        self.__doc__ = function.__doc__

    def __call__(self, *args, **kwargs):
        key = (pickle.dumps(args), pickle.dumps(kwargs))
        if key in self.cache:
            result = self.cache[key]
        else:
            result = self.cache[key] = self.function(*args, **kwargs)
            print(f"Cached Value: {result}")
        return result
In [0]:
@Cache
def fib(n):
    """ Recursive Fibonacci """
    return 1 if n < 3 else fib(n - 1) + fib(n - 2)


print(fib(10))
print(fib(12))  # Notice the cache only caches new values.
Cached Value: 1
Cached Value: 1
Cached Value: 2
Cached Value: 3
Cached Value: 5
Cached Value: 8
Cached Value: 13
Cached Value: 21
Cached Value: 34
Cached Value: 55
55
Cached Value: 89
Cached Value: 144
144
In [0]: