In the spirit of the wonderful "Twisted web in 60 seconds" posts, I'm going to contribute here a small post about the Twisted reactor and Deferreds. I know this can be found on the Twisted examples page, but writing it from scratch was helpful for my understanding as well :)

A mainloop

The most usual problem when trying to understand the reactor is context. In my case, the penny dropped when I saw the reactor in the context of a plain desktop application - not a script, not a web server.

Desktop applications are built around and event loop - the user moves the mouse, presses keys, the windowserver captures those events and calls various event handlers on your application.

Twisted reactor is a similar thing:

from twisted.internet import reactor

reactor.run() # this blocks!

After you call reactor.run, your program has an event loop, and the reactor takes over. When embedding Twisted in desktop applications, you need to install a reactor compatible with your main loop (gtk, cocoa and so on).

Deferreds

Event handlers in twisted parlance are called "deferreds". When you ask twisted to do something asynchronously, be it fetching a web page, sending an email, resolving a DNS name, connecting to a server, you need a way to know when that action succeeded (or failed, of course.)

Twisted deferreds can support chaining the results. More on this on a later example, though.

An example

Think about the canonical example when doing desktop application programming. You want to fetch some information from a webserver (perhaps to check for updates). Normally you would spawn a thread, fetch the page, process it, then update your user interface (of course taking care to update the UI from the GUI thread and other similar arcane issues).

In twisted there is only a single thread, and everything is set up using deferreds and the reactor. Let's write some simple code to do that:

We'll need to import the reactor and a getPage function:

from twisted.internet import reactor
from twisted.web.client import getPage

This is a desktop application, so here's a small function to update the UI:

def updateUI(message):
    print "UI:", message

Let's write our processing function:

def processPage(pageContent):
    print 'got the page'
    # process the page here
    # ...
    # update the UI
    updateUI('SUCCESS')

Remember, this will get called when twisted has fetched the page. Its return value will be passed on to the next callback down the chain. Here, we just return the length of the page.

Let's also write an error handler:

def handleError(error):
    print 'got error'
    # update the UI
    updateUI('Whoops! %s' % error)

Having written all the pieces, let's bring them together:

pageFetchedDeferred = getPage("http://orestis.gr")
pageFetchedDeferred.addCallback(processPage)
pageFetchedDeferred.addErrback(handleError)

Finally, we need to start the reactor. In a desktop app, you would've done that at the final stage of initialisation, but this is an example, right?

reactor.run()

Here's the whole program:

from twisted.internet import reactor
from twisted.web.client import getPage

def updateUI(message):
    print "UI:", message

def processPage(pageContent):
    print 'got the page'
    # process the page here
    # ...
    # update the UI
    updateUI('Received %d bytes' % len(pageContent))

def handleError(error):
    print 'got error'
    # update the UI
    updateUI('Whoops! %s' % error)

pageFetchedDeferred = getPage("http://orestis.gr")
pageFetchedDeferred.addCallback(processPage)
pageFetchedDeferred.addErrback(handleError)

reactor.run()

Remember, you need to kill your program with Ctrl-C because the reactor.run call blocks!

Conclusion

If you understand how deferreds and the reactor works, you've made an important step on using Twisted. As I use and understand Twisted more, I'll keep writing!

September 25, 2009, 10:59 a.m. More (578 words) 2 comments Feed
Previous entry: Athens Python UG - 1st meeting results
Next entry: How to send an email with Twisted

Comments

1

Comment by Evan , 2 years, 7 months ago :

Thanks for your sharing, please keep writing !! :D

2

Comment by David Reid , 2 years, 7 months ago :

Wonderful post! My only complaint is that you imply a relationship between the reactor and Deferreds that doesn't exist.

The reactor has existed long before Deferreds did and does not depend or use Deferreds in any way. The same is true for Deferreds, they have no dependency on the reactor and could be easily taken and used for non-Twisted code.

Implying this relationship makes Deferreds somehow seem special, they're not they are just a generic data structure for representing eventual results and the actions to be taken on them.

But really it's a great post, I hope to read more soon!

-David


This post is older than 30 days and comments have been turned off.