20 January 2011

3 decorator examples & other awesome things about Python

A decorator is a function that takes another function and returns a newer, prettier version of that function.*

Here are three examples of Python decorators for people who know how to use them, but forget how to write them. If you make it past the examples, there is further discussion of some of the awesome features of Python that are utilized in the code below.

Also, these specific examples are for Django views, but you can generalize it to any function by pulling out the request argument.

Basic decorator:

from functools import wraps

def my_decorator(view_func):
    def _decorator(request, *args, **kwargs):
        # maybe do something before the view_func call
        response = view_func(request, *args, **kwargs)
        # maybe do something after the view_func call
        return response
    return wraps(view_func)(_decorator)

# how to use it...
def foo(request): return HttpResponse('...')
foo = my_decorator(foo)

# or...
@my_decorator
def foo(request): return HttpResponse('...')

(the wraps function is explained below)

Parameterized decorator:

This one allows you to pass arguments into the decorator for some additional customization. It needs to wrap everything in an additional function (creating a closure) in order to make this possible.

from functools import wraps

def my_decorator(extra_value=None):
    def _my_decorator(view_func):
        def _decorator(request, *args, **kwargs):
            # maybe do something before the view_func call
            # that uses `extra_value` and the `request` object
            response = view_func(request, *args, **kwargs)
            # maybe do something after the view_func call
            return response
        return wraps(view_func)(_decorator)
    return _my_decorator

# how to use it...
def foo(request): return HttpResponse('...')
foo = my_decorator('some-custom-value')(foo)

# or...
@my_decorator('some-custom-value')
def foo(request): return HttpResponse('...')

A class decorator:

from functools import wraps

class my_decorator(object):

    def __init__(self, view_func):
        self.view_func = view_func
        wraps(view_func)(self)

    def __call__(self, request, *args, **kwargs):
        # maybe do something before the view_func call
        response = self.view_func(request, *args, **kwargs)
        # maybe do something after the view_func call
        return response

# how to use it...
def foo(request): return HttpResponse('...')
foo = my_decorator(foo)

# or...
@my_decorator
def foo(request): return HttpResponse('...')

Some awesome things about Python…

Passing through positional and keyword arguments:

(A) You usually want the decorated function to take the same arguments as the original function, and (B) Django views always start with a request, but they may have any number of additional arguments. Fortunately you can rely on *args and **kwargs to catch the extra positional arguments in a tuple and the extra keyword arguments in a dictionary. You can easily pass these through to the view function, and potentially read or modify them inside your decorator too.

Callable objects:

I can’t say I really use classes for decorators much at all, but it’s a fun example to show off that the __call__ function makes any instance of a class callable like a regular function.

Wrap it up!

The functools.wraps function is a nice little convenience function that makes the wrapper function (i.e., the return value from the decorator) look like the function it is wrapping. This involves copying/updating a bunch of the double underscore attributes—specifically __module__, __name__, __doc__, and __dict__. See more in the update_wrapper function in the functools source code.

Also, if you’re an astute reader, you may have noticed that wraps is a decorator, and it is generally used inside other decorator functions. This is why my friend Harold likes to refer to it as an interior decorator.

Partial Function Application:

If you read through the source code for wraps in functools, then you saw that it uses the partial function. Partial is awesome—it’s sort of like currying. It lets you create a new function from an existing function with some of the arguments predefined. Here’s a relatively trivial example that makes a new min function by setting its “key” argument to be absolute value:

from functools import partial
min_abs_val = functools.partial(min, key=abs)
min_abs_val(-5, -1, 2) == -1

You can see it in action in the functools source code, but if you’d prefer a convoluted explanation of how it’s used… you can see that the wraps function is a decorator that is cleverly using the partial function to return a partial function of the update_wrapper function, which now only needs the wrapper function argument, and that argument will be the function that is decorated inside of a decorator function that will eventually wrap other functions… INCEPTION

Happy decorating!

Comments (1)

1. rulman wrote:

thanks very much

Posted on 31 March 2011 at 10:03 AM  |  permalink

Peter Coles

Peter Coles

is a software engineer who lives in NYC, works at Hunch/eBayNYC, and blogs here.
More about Peter »

@lethys · github · hunch
rss