Someone recently wrote to me asking about decorators, and saying they found them a bit confusing. Here's a post based on the email I replied to them with.
The best way to understand decorators is to build a couple of them, so here are two examples for you to try out. The first is in the Django world, the second is actually a simpler, pure-python one.
We've built a very basic todo lists app using Django. It has views to deal with viewing lists, creating new lists, and adding to existing lists. Two of these views end up doing some similar work, which is to retrieve a list object from the database based on its list ID:
def add_item(request, list_id):
list_ = List.objects.get(id=list_id)
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/%d/' % (list_.id,))
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
return render(request, 'list.html', {'list': list_})
(Full code here)
This is a good use case for a decorator.
A decorator can be used to extract duplicated work, and also to change the arguments to a function. So we should be able to build a decorator that does the list-getting for us. Here's the target:
@get_list
def add_item(request, list_):
Item.objects.create(text=request.POST['item_text'], list=list_)
return redirect('/lists/%d/' % (list_.id,))
@get_list
def view_list(request, list_):
return render(request, 'list.html', {'list': list_})
So how do we build a decorator that does that? A decorator is a function that takes a function, and returns another function that does a slightly modified version of the work the original function was doing. We want our decorator to transform the simplified view functions we have above, into something that looks like the original functions.
(you end up saying "function" a lot in any explanation of decorators...)
Here's a template:
def get_list(view_fn):
def decorated_view(...?):
???
return view_fn(...?)
return decorated_view
Can you get it working? Thankfully, our code has tests, so they'll tell you when you get it right...
git clone -b chapter_06 https://github.com/hjwp/book-example
python3 manage.py test lists # dependencies: django 1.7
Decorators definitely are a bit brain-melting, so it may take a bit of effort to wrap your head around it. Once you get the hang of them, they're dead useful though,
If you're finding it impossible, you could start with a simpler challenge... say, building a decorator to make functions return an absolute value:
def absolute(fn):
# this decorator currently does nothing
def modified_fn(x):
return fn(x)
return modified_fn
def foo(x):
return 1 - x
assert foo(3) == -2
@absolute
def foo(x):
return 1 - x
assert foo(3) == 2 # this will fail, get is passing!
Try it out:
git clone https://gist.github.com/2cc523b66d9c0fe41c4b.git deccy
python3 deccy/deccy.py
Enjoy!
[update 2014-10-23 at 3pm, see also @baroque, the decorating decorator decorator]
The book is available both for free and for money. It's all about TDD and Web programming. Read it here!
"Hands down the best teaching book I've ever read" — "Even the first 4 chapters were worth the money" — "Oh my gosh! This book is outstanding" — "The testing goat is my new friend" — Read more...
A selection of links and videos about TDD, not necessarily all mine, eg this tutorial at PyCon 2013, how to motivate coworkers to write unit tests, thoughts on Django's test tools, London-style TDD and more.
This is my old TDD tutorial, which follows along with the official Django tutorial, but with full TDD. It badly needs updating. Read the book instead!
The campaign page, preserved for history, which led to the glorious presence of the Testing Goat on the front of the book.
Comments
comments powered by Disqus