More Python features that I really like

Another thing that makes using [Python][python] pleasing is decorators. [A decorator is a wrapper for a function][decorators] (or method) that takes a function (or method) as an argument and returns a new function (or…) which is then bound to the name for the original function.

The newly-decorated function can then do things like checking the called arguments before invoking the original un-decorated function.

[Django provides decorators for authentication][django] so that you can wrap a view function with a check for client credentials before deciding whether to return the original response or a deny access.

In this manner Django’s authentication decorators encourage orthogonal code: the logic for displaying a view is separated from the logic for deciding whether you should be permitted to see the view’s output. By keeping them separate, it becomes simpler to re-use the authentication logic and apply it to other views.

Suppose you have a view that accepts [a Django request object][request] and checks whether the user is signed in:

def administration_page(request):
if request.user.is_authenticated():
return HttpResponse(“Welcome, dear user.”)
else:
return HttpResponseRedirect(“/signin/”)

With a decorator you can simplify and clarify things:

@login_required
def administration_page(request):
return HttpResponse(“Welcome, dear user.”)

For older versions of Python (pre 2.4) [which don’t understand the `@` operator][syntax] one must explicitly decorate the view function like so:

def administration_page(request):
return HttpResponse(“Welcome, dear administrator.”)

administration_page = login_required(administration_page)

Note in the example that the original `administration_page` function is passed to the decorator. The `@` syntax in the first example makes that implicit but the two are equivalent.

The implementation of a decorator is interesting. It takes the function itself as an argument and returns a new function which does the actual checking. Here is how the decorator used above might do its stuff:

def login_required(view_function):
def decorated_function(request):
if request.user.is_authenticated():
return view_function(request)
else:
return HttpResponseRedirect(“/signin/”)

return decorated_function

_The actual [implementation of Django’s `login_required` decorator][login_required] is considerably less idiotic. Python’s [functools module][functools] has helpers for writing well-behaved decorators._

Because functions in Python are themselves objects the decorator can accept a function reference, construct a new function that checks for authentication and then return a reference to that new function.

Simples!

(Simples gets less simples when you want to write a decorator that accepts configuration arguments because you then need either another layer of nested function definitions or a class whose instances can be called directly, but I’m going to ignore you for a bit and _wow is that Concorde…?_)

[python]: http://www.python.org
[decorators]: http://docs.python.org/reference/compound_stmts.html#function
[django]: http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.decorators.user_passes_test
[request]: http://docs.djangoproject.com/en/dev/ref/request-response/
[syntax]: http://docs.python.org/whatsnew/2.4.html#pep-318-decorators-for-functions-and-methods
[functools]: http://docs.python.org/library/functools.html
[login_required]: http://code.djangoproject.com/browser/django/tags/releases/1.2.1/django/contrib/auth/decorators.py

Leave a Reply

Your email address will not be published. Required fields are marked *