Python Decorators

Decorators in Python are just fantastic. Here are a few I've used in Postr. Update: the wonderful James Hensbridge expanded the decorators, so I've updated this post.

def threaded(f):
    def wrapper(*args, **kwargs):
        t = threading.Thread(target=f, args=args, kwargs=kwargs)
        t.setDaemon(True)
        t.start()
    wrapper.__name__ = f.__name__
    return wrapper

This decorator (stolen from O'Reilly) calls the method in a new thread, so execution returns straight away. I use this for long-running tasks that cannot be handled in an asynchronous manner (such as the photo uploading in Postr, which currently uses urllib). The main thread returns straight away so the interface doesn't block, and the uploads continue in the background.

Of course this new thread cannot just call GTK+ methods, as GTK+ itself isn't threadsafe. So, I have another decorator that causes a method to be always executed in the main loop by scheduling an idle handler that calls it.

def as_idle(f):
    def wrapper(*args, **kwargs):
        event = threading.Event()
        ret = []
        def task():
            ret.append(f(*args, **kwargs))
            event.set()
            return False
        gobject.idle_add(task)
        event.wait()
        return ret[0]
    wrapper.__name__ = f.__name__
    return wrapper

Erich Schubert requested return values, so James added those too. The calling function will block until the idle handler has called the decorated function. I've split the decorators out into a separate file in Postr, so you can view the latest version online.

Magic stuff!

21:30 Monday, 11 Sep 2006 [#] [computers] (8 comments)

Posted by Jon Dowland at Mon Sep 11 22:39:55 2006:
postr eh? Sounds interesting. I've just done a batch with the kflickr in etch and I'm not too impressed.
Posted by Ian at Mon Sep 11 23:13:47 2006:
They're perfect for implementing Aspect-Oriented techniques too.
Posted by James Henstridge at Tue Sep 12 01:14:41 2006:
Two things you might want to add in your @threaded decorator:

1. make the wrapper function "def wrapper(args, *kwargs)", and pass the kwargs on to the original function.

2. "wrapper._name_ = f._name_" -- this way your methods won't all claim to be called wrapper.
Posted by erich at Tue Sep 12 01:26:45 2006:
Now if you could make the second call back with return values you'd be my hero.
Say... I have this long-running task in the background; when it's finished it will notify the user - e.g. via a button being ungreyed, a popup message, whatever - to decide on what to do next. Then it will go back to work.

The threadsafe decorator is nice for updating the UI, but I'd actually like it to block my long-running task until the user has made his decision. That way all my local variables etc. remain intact.
Posted by Russel Winder at Tue Sep 12 06:41:38 2006:
Perhaps the source for javax,swing.SwingUtilities.invokeLater
and javax.swing.SwingUtilities.invokeAndWait
would give ideas on how to do all the thread manipulation?

I suspect all this sort of stuff is a lot easier in languages with closures such as Ruby or Groovy.
Posted by Murray Cumming at Tue Sep 12 07:03:48 2006:
It's not the point, but what's that *arg syntax called in Python. and what does it do? It looks like I'm missing some basic Python knowledge.
Posted by Ian at Tue Sep 12 10:10:59 2006:
*args is just an array of arguments of unknown size:

func(1, 2, 3) and func(1) are both valid, they would result in arg = [1, 2, 3] and arg = [1], respectively.

**kwargs is similar but as a dictionary (called keyword arguments, kwargs for shot):

func({"one": 1, "two": 2})
Posted by Russel Winder at Tue Sep 12 13:03:51 2006:
I think you will find that *args is a tuple not a list (Python doesn't have arrays :-)

Name:


E-mail:


URL:


Add 3 and 2 (required):


Comment: