#6681 enhancement new
'after' decorator, for doing things after other things
|Reported by:||glyph||Owned by:|
Today, in Twisted, if you want to add a callback to a Deferred, there are multiple possible idioms. There's this:
foo().addCallback(lambda bar: baz())
but of course that only works if your Python code fits into an expression, which it rarely does. So then there's this:
def onFoo(result): ... foo().addCallback(onFoo)
which is serviceable enough, but can be confusing, especially if onFoo is more than a couple of lines long. It's not clear that 'foo' is going to happen until 'onFoo' has already been designed. So then there's this:
d = foo() def onFoo(result): ... d.addCallback(onFoo)
and that's a bit more straightforward, but now you've had to write an additional line of code, come up with an additional temporary name (inevitably: d, d1, d2, d3), and pollute your namespace with two names that you're not going to use any more after the addCallback call.
It would be nice to use a decorator for this, but:
@foo().addCallback def onFoo(result): ...
is a syntax error, because I guess Python doesn't have a real parser and can't tell what an expression is? So, I've started resorting to this, in some places:
from twisted.internet.defer import passthru @passthru(foo().addCallback) def onFoo(result): ...
and that works if you know what the point is, but it is somewhat obscure.
I propose a new function in twisted.internet.defer, after, so that we can get the benefits of a decorator (fewer redundant temporary variables, operation comes lexically before the consequences of the operation) with better readability than the passthru hack.
This would be used like so:
from twisted.internet.defer import after @after(foo()) def fooDone(result): ...
The after decorator might be trivially implemented like so:
def after(deferred): return deferred.addCallback
Although perhaps a more robust implementation would return the function itself, to reduce confusion; I'm open to suggestions there.
This does beg for some symmetry with error handling, which could be used like so:
@after(foo()) def fooDone(result): ... @catch(ZeroDivisionError, fooDone) def fooOops(why): ...
which could then be implemented like so:
def catch(exceptionType, deferred): def addIt(handler): def errback(failure): failure.trap(exceptionType) return handler(failure) return handler.addErrback(errback) return addIt
The implementation here is trivially easy, but I think we should have some good consensus before heading in. I know some people feel this syntax is gross, and I'd like to get some actual explanation of why.