My misinterpretation of Python decorators

DrProject is written in Python, but I had only a little experience with Python when I first started. So I had to learn many things along the way, sometimes in the hard way.

Today’s topic is the semantics of decorators in Python (PEP 318). I used to think I knew how they worked, but I was wrong. Let’s start with a simple example:

def decorate(func):
    def decorated(self):
        print "Before"
        func(self)
        print "After"
    return decorated

class Foo(object):
    @decorate
    def bar(self):
        print "Hello, world!"

This is equivalent to the following code in the old style (I won’t repeat decorate because it remains unchanged):

class Foo(object):
    def bar(self):
        print "Hello, world!"
    bar = decorate(bar)

In either case (they’re equivalent), if we create a Foo and invoke bar, we get:

Before
Hello, world!
After

So far so good? This is the kind of code I found in DrProject:

def action(name, template=None, stylesheets=None):
    def _decorate(func):
        def _execute(controller, req):
            (...)
        (...)
        return _execute
    (...)
    return _decorate

class AdminController(Controller):
    @action('list_projects',
        template='admin_list_projects.html',
        stylesheets=['admin.css'])
    def _process_list_projects(self, req, order='name', desc=''):
        (... omitted ...)

Somehow, I naïvely expected the decorator invocation to be equivalent to something like _process_list_projects = action(_process_list_projects, 'list_projects', template='admin_list_projects.html', stylesheets=['admin.css']), with the function as the zeroth argument. But that would be impossible, because action only takes 3 arguments, and has no provisions for extra arguments – so Python would err loudly if action is called with 4 arguments.

The key to understanding this situation lies in PEP 318, but the code itself is very suggestive too. Essentially, _process_list_projects is decorated by the function returned by action('list_projects', template=..., stylesheets=...). (See how action returns a nested _decorate function?) So overall, the explicit form of this decoration would be as follows:

class AdminController(Controller):
    def _process_list_projects(self, req, order='name', desc=''):
        (... omitted ...)
    _process_list_projects = action('list_projects',
        template='admin_list_projects.html',
        stylesheets=['admin.css'])(_process_list_projects)

Notice the double call, i.e. action(...)(...).

To recap, a decorator usage like

@decorate
def foo():
    (...)

expands to the form

def foo():
    (...)
foo = decorate(foo)

where decorate is an arbitrary expression that evaluates to a decorator, which can be a simple function variable or the result of a function maker (e.g. make_decorator(1,2,3)).

I have nothing against the decorator syntax; in fact, I think it makes a lot of sense now that I understand it better. I just don’t want anyone else to repeat the same naïve misinterpretation.

Advertisements
Explore posts in the same categories: Python, Uncategorized

2 Comments on “My misinterpretation of Python decorators”

  1. Bill Mill Says:

    That PEP is kind of difficult to grok. I remember I had to read it about 10 times to figure out exactly how to write my redecorator, which makes all decoration explicit.

    I don’t hate decorators as much nowadays, though I still think they’re overussed.


  2. Yup, decorators are silly :(

    I got confused by the same thing the other day… It’s not fun.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: