Wednesday, April 26, 2006

Python AST Manipulation for Transparent Defering of Calls?

A lot of work has been going on with the Python AST and being able to manipulate it for more runtime uses. This is a generalized suggestion for something we could do in the Twisted community to utilize this.

Given a simple function like this:



def processPage(url):
d = getPage(url)
d.addCallback(cb_processPage)
d.addErrback(eb_processPage)
def cb_processPage(page):
print page
def eb_processPage(error):
print "Could not load page. Error: ", error

We write like that, but what we really mean, and just need to express in a more difficult manner, is:


def processPage(url):
try:
print getPage(url)
except LoadError, e:
print "Could not load page. Error: ", e

What I want to know, is can we take the second example and process the AST branch to produce the first example? Lets step through and see how it would work. First of all, we need to know what is deferred. A simple way would be to check every function return and determine if it is a deferred, but there may be more efficent methods we can discover later. For any possibly deferred call, the expression it is a part of can be refactored out into its own function, as can the exception handler code. When a deferred is detected, the callbacks can be attached and if the operation is not deferred, the callbacks can be used directly. This might even bring about something slightly new: optionally deferred operations. Instead of using defer.succeed and such, returning a deferred could cause code to handle the deferred properly, which would otherwise act in a normal syncronous manner. Depending on just how the AST stuff progresses, maybe the returning of the deferred could trigger the inspection of the calling function to inject the deferred handling code, so only functions that ever get a deferred will need processing.

2 comments:

PJE said...

"""What I want to know, is can we take the second example and process the AST branch to produce the first example?"""

Doesn't matter. Change your second example to say "print yield getPage(url)", use Python 2.5, and run the whole thing under a trampoline function that knows how to handle deferreds. See PEP 342 for examples. Net result: all you have to do is add "yield" wherever your code can be suspended -- even in the middle of arbitrary expressions, error handlers, and the like.

Now, if you want to do it with Python versions before 2.5, see peak.events. You just have to make it say "yield getPage(url); print events.resume()" instead, because Python before 2.5 can't get a value back from a yield statement. But peak.events already has the necessary trampolines written, and they already work with Twisted's deferreds.

Tristan Seligmann said...

I'd prefer to write something like this (replacing indentation with periods for legibility's sake):

def processPage(url):
....when getPage(url) -> page:
........print page
....except LoadError, e:
........print 'Could not load page. Error: ', e

I write here about programming, how to program better, things I think are neat and are related to programming. I might write other things at my personal website.

I am happily employed by the excellent Caktus Group, located in beautiful and friendly Carrboro, NC, where I work with Python, Django, and Javascript.

Blog Archive