Python function decorators (PEP 318) make it easy to modify behavior of functions (or add new functionality), without changing them. We won't introduce them here as they're documented extensively.

We're demonstrating a specific case, where we have a class and we want to to use one of the methods of that class, as a function decorator. For example the Timer class provies the method time_callable() method to measure and report how much time it takes to execute functions.

class Timer(object):
    def __init__(self, reporter):
        ...

    def time_callable(self, func):
        ...

timer = Timer(Reporter())
timer.time_callable(my_func) # call my_func and report the duration

Say we'd like to always measure and report a function like my_func in a project. Using a function decorator seems like a good choice for this kind of functionality. If we decorate my_func with the measuring function, there is no need to replace all the my_func calls with time_callable(my_func).

So we're going to add a method wrap() to the Timer class, to decorate any function by measuring and reporting the duration. Plus we assume we'd like our decorator to accept specific parameters, for example a name to assiciate with the timed functions to appear in our reports. This is how the method is going to be called:

timer = Timer(Reporter())

@timer.wrap("my_func_duration")
def my_func(arg1, arg2, key=value): # the function may need arguments
    ...

We're going to implement Timer.wrap(). But first let's start simple, by defining the decorator as just a function wrap() (without extra parameters and no self reference).

def wrap():
    ...

@wrap
def my_func(arg1, arg2, key=value):
    ...

my_func("foo", "bar", key="baz")

When a function is decorated by another, Python runtime replaces references to the decorated function, with the return value of calling the decorator function, passing the decorated function as argument. In this example all the references to my_func are replaced by wrap(my_func). Therefor:

my_func("foo", "bar", key="baz")

is replaced by:

wrap(my_func)("foo", "bar", key="baz")

So wrap should accept my_func (or any function), and return another function. The returned function should accept all the arguments of my_func since the callers of my_func are expecting to pass them.

Now we know what to implement for wrap (we're also using functools.wraps to update the metadata of our decorator function to look like the decorated one).

from time import time
import functools

def wrap(func):
    @functools.wraps(func)
    def decorator(*args, **kwargs):
        start_timestamp = time()
        func(*args, **kwargs)
        print(time() - start_timestamp)

    return decorator

@wrap
def my_func(arg1, arg2, key):
    ...

my_func("foo", "bar", key="baz")

We're printing the duration of the call here, otherwise there would be no difference between the decorated my_func and none-decorated.

Back to our main intention. Let's compare how we're using wrap now, with what we want Timer.wrap to be:

@wrap
def my_func(arg1, arg2, key):
    ...

@timer.wrap("my_func_duration")
def my_func(arg1, arg2, key):
    ...

This shows that the Timer.wrap should be a method accepting a parameter for the report name (and of course self), and it should return a function that will behave like wrap. From the runtime perspective, the calls to my_func:

my_func("foo", "bar", key="baz")

are going to be replaced by:

timer.wrap("my_func_duration")(my_func)("foo", "bar", key="baz")

Now we know exactly what to implement:

from time import time
import functools

class Timer(object):
    def __init__(self):
        pass

    def wrap(self, name):
        def create_decorator(func):
            @functools.wraps(func)
            def decorator(*args, **kwargs):
                start_timestamp = time()
                func(*args, **kwargs)
                print("{}: {}".format(name, time() - start_timestamp))
            return decorator
        return create_decorator

timer = Timer()

@timer.wrap("my_func_duration")
def my_func(arg1, arg2, key):
    ...

We ignored the Reporter() and just printed the duration, to focus on the implementation of the decorator itself.