Sunday, October 28, 2012

Python Function Decorators

One of the (many) neat things about Python is the fact that functions are first class objects (like in JavaScript). As the first class nature of functions has been covered many times elsewhere (take a look at this and this), I shall not talk about what that means. However, one of the things one can do with first class functions is pass them as an argument to another function. The ability to do this is what enables the use of function decorators. Again, if you really want to understand how decorators work, why the work, etc., then I suggest you read this excellent explanation. If however, you just want to see an example so that you can use it in your own code, keep reading.

There are two types of decorators, those without arguments (e.g @mydecorator) and those with arguments (e.g. @mydecorator('foo', 'bar'). For the case with no arguments, the code for your decorator will look as follows:
import functools

def mydecorator(f):
    """A simple decorator."""
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print 'Before %s in wrapper' % f.__name__
        result = f(*args, **kwargs)
        print 'After %s in wrapper' % f.__name__
        return result
    return wrapper

@mydecorator
def myfunc():
    print 'Inside myfunc'

In case you are curious, we don't technically need to use the @functools.wraps decorator in our decorator, but it performs some very useful things for us. Take a look at this post if you want to know why we 'need' it.

If you want to pass arguments to your decorator, then your code will need be something like this:
import functools

def mydecorator(arg1, arg2):
    """A simple decorator with arguments."""
    def wrap(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print 'Decorator arguments: %s, %s' % (arg1, arg2)
            print 'Before %s in wrapper' % f.__name__
            result = f(*args, **kwargs)
            print 'After %s in wrapper' % f.__name__
            return result
        return wrapper
    return wrap

@mydecorator('foo', 'bar')
def myfunc():
    print 'Inside myfunc'


It is very important to note that the arguments passed to the decorator are only evaluated once, the first time that the function is encountered. This essentially means that you cannot change the parameters to your decorator at runtime and if arg1 was 'foo', then it will be 'foo' until your program terminates. If you can look at the code above and understand why that is at first glance, then that is admirable. If you are like the rest of us, check out the great explanation of decorators here.

No comments:

Post a Comment