Saturday, July 13, 2013

Using Python decorators for registering callbacks

In my previous post we talked about Python decorators and an intuitive way of remembering how decorators with arguments work.

The following code snippet had triggered the whole chain of thought:

from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, World!"
if __name__ == '__main__':
app.run(debug = True)
view raw flask_1 hosted with ❤ by GitHub

Now that we know what decorators with arguments do, which is essentially - calling the decorator factory with the argument, using the argument to make logical branching in the decorator wrapper and returning the wrapper, we can now try to understand the above code. We first move on to the decorator route's source code found here to see it's implementation. Keep the source code opened in a different tab, as we will refer to it in the later sections.

def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator


This code deviates a bit from what we think about how decorators are used - decorators wrap the functionality of a target function with certain pre and post actions, like so:

#!/usr/bin/env python
def decorator(func):
def wrapper_around_func():
print "Decorator calling func"
func()
print "Decorator called func"
return wrapper_around_func
@decorator
def func():
print "In the function"
func()
view raw decorator-ex1 hosted with ❤ by GitHub


Output:
 Decorator calling func
 In the function
 Decorator called func

But there isn't any call to the target function index in the route function definition. Instead, this code snippet throws light on another functionality of decorators - registering callbacks.

Callbacks are registered functions which are stored in some container data structure (mostly hashes with key as function name and value as function references). For example,

#!/usr/bin/env python
def func1(arg):
print "calling func1 with arg " + str(arg)
def func2(arg):
print "calling func2 with arg " + str(arg)
callbacks_dict = { 'func1': func1, 'func2': func2 }
callbacks_dict['func1'](1)
callbacks_dict['func2'](2)

Output:
  calling func1 with arg 1
  calling func2 with arg 2

They are useful in building a map of functions and depending on specific user input, the hashes are looked up and the corresponding key's value - which is a function reference is called.

Monday, June 10, 2013

Thinking out aloud: Python decorators

This is not yet another explanation of decorators. There are a lot of people who have done a great job of explaining it. What this post is about is the way I understood it and how I wrap my head around decorators (pun intended).

Pre-requisite: this awesome stackoverflow answer on decorators.

I had been trying to learn Flask and came across this nice post. It contains a good introduction to flask in the form of a mini project. While going through the post, I came across this snippet:

from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, World!"
if __name__ == '__main__':
app.run(debug = True)
view raw flask_1 hosted with ❤ by GitHub

@app.route('/') is a decorator which registers the index function to be called when a user makes GET requests to the root page.

I always get the part where we say that a decorator is a function which modifies another function - wraps it with a sort of pre and post functionality. The part of where decorators are passed arguments is what used to confuse me and led me to revisit the afforementioned stackoverflow post

For example, it is easy to understand this:

#!/usr/bin/env python
def decorator(func):
def wrapper_around_func():
print "Decorator calling func"
func()
print "Decorator called func"
return wrapper_around_func
@decorator
def func():
print "In the function"
func()
view raw decorator-ex1 hosted with ❤ by GitHub


Output:

Decorator calling func
In the function
Decorator called func


which means that when Python encountered the @ symbol it did an internal equivalent of
func = decorator(func)

which in turn means that a decorator is a function which takes in a function and returns a wrapper over that function and reassigns that wrapper to the original function variable.

This has the side effect of redefining the function name also (func.__name__), to be that of the wrapper function, but as the stackoverflow answer mentions functools.wraps comes to the rescue.

What used to stall me were these kind of examples:

#!/usr/bin/env python
import inspect
from functools import wraps
def get_line_number():
# inspect the current frame, go back 2 frames and get the line number.
return inspect.currentframe().f_back.f_back.f_lineno
def log_decorator(loglevel):
def log_decorator_creator(func):
@wraps(func)
def wrapper_around_func(*args):
print func.__name__,
if (loglevel == 'debug'):
print " called_from_line:" + str(get_line_number()),
print " args - [" + ", ".join(args) + "]"
return wrapper_around_func
return log_decorator_creator
@log_decorator('info')
def func1(arg1, arg2):
print "entering func1"
@log_decorator('debug')
def func2(arg1, arg2):
print "entering func2"
func1('this', 'that')
func2('who', 'what')
view raw decorator-ex2 hosted with ❤ by GitHub


Output:


func1 args - [this, that]
func2 called_from_line:31 args - [who, what]


For func1 our logging is not as verobse as func2 which has 'debug' log level. This is possible because the decorator creating function takes an argument which decides logging behavior (whether to print line no. of caller or not).

The magic of closures is also involved because the decorator and the wrapped function remember the original environment that they were bound with (for func1 the variable loglevel is set to 'info' in wrapped_around_func and for func2, it is set to 'debug')

But what is the deal with the nesting of functions?