So, decorators are neat (maybe check out a new tutorial on them). Descriptors are neat but may seem hard (though they hardly take a long time to describe). Sometimes these two things intersect, and this post describes how.
Here’s an example decorator where this comes up. First, we want something that looks like a WSGI application:
def application(environ, start_response):
start_response('200 OK', [('content-type', 'text/html')])
return ['hi!']
But we want to use WebOb, like:
from webob import Request, Response
def application(environ, start_response):
req = Request(environ)
resp = Response('hi!')
return resp(environ, start_response)
(We don’t use req in this example, but of course you probably would in a real WSGI application)
Now req = Request(environ) is boilerplate, and it’d be nicer to just do return resp instead of return resp(environ, start_response). So let’s make a decorator to do that:
class wsgiapp(object):
def __init__(self, func):
self.func = func
def __call__(self, environ, start_response):
resp = self.func(Request(environ))
return resp(environ, start_response)
@wsgiapp
def application(req):
return Response('hi!')
If you don’t understand what happened there, go read up on decorators.
Now, what if you want to decorate a method? For instance:
class Application(object):
def __init__(self, text):
self.text = text
@wsgiapp
def __call__(self, req):
return Response(self.text)
application = Application('hi!')
This won’t quite work, because @wsgiapp will call Application.__call__(req) — with no self argument. This is generally a problem with any decorator that changes the signature, because the signature for methods has this extra self argument. Descriptors can handle this. First we’ll have the same wsgiapp definition as we had before, but we’ll add the magic descriptor method __get__:
class wsgiapp(object):
def __init__(self, func):
self.func = func
def __call__(self, environ, start_response):
resp = self.func(Request(environ))
return resp(environ, start_response)
def __get__(self, obj, type=None):
if obj is None:
return self
new_func = self.func.__get__(obj, type)
return self.__class__(new_func)
So, to explain:
When you get an attribute from an instance, like Application().__call__, Python will check if the object that was fetched has a __get__ method. If it does, it will call that method and use the result of that method.
This part:
if obj is None:
return self
is what happens when you do Application.__call__ — in other words, when get a class attribute. In that case obj (self) will be None, and it will just return the descriptor (it could do something else, like in this example, but usually it doesn’t).
Functions already have a __get__ method. You can try it yourself:
>>> def example(*args):
... print 'got', args
>>> example_bound = example.__get__(1)
>>> example_bound('test')
got (1, 'test')
So in the example with wsgiapp we are just changing the decorator to wrap the new bound function instead of the old unbound function. This allows wsgiapp to be compatible with both plain functions and methods. In fact, it would probably be preferable to always call func.__get__(obj, type) (even if obj is None), as then we could also wrap class methods or other kinds of descriptors.
Fascinating post Ian, thanks! Descriptors and decorators can be so helpful make code cleaner and more decoupled, it’s a shame they are perceived as so “difficult”. They do take a bit of effort to learn I guess, but it’s WELL worth it! [ed. note: correction applied, thanks]
Cheers, Ben
I’ve just had a play with some of this… It never occured to me but you can use this to make new methods after instantiation:
t.times2(4)now acts like a normal method and gives8.I don’t quite understand it all yet, but this solves an old problem I had in TurboGears where I was trying to abstract controllers. I would make a class Foo, and ran up against this:
I came up with an ugly hack making them all static methods, but with Ben’s deco class, I can now do this and it all works:
I think it should be
application = Application("some text")instead of
application = Application[fixed, thanks!]
In the first decorator (class wsgiapp), call takes two arguments other than self (environ, response). But the function it’s decorating, application, takes just one parameter. Is this right?
Very nice. It does seem like the
__get__method of the decorator would get called on every call of the wrapped method though. Would there be any way of instantiating the bound method on first call and on all subsequent calls just using the previously instantiated method?If anyone is interested, here’s a set of decorators I’m using all the time: http://svn.pythonpaste.org/Paste/WebOb/contrib/decorators.py