[Twisted-Python] Thoughts on Deferred
Clark C. Evans
cce at clarkevans.com
Thu Mar 6 01:02:23 EST 2003
Ok. I've been using Deferreds some time now, actually I've
coverted my report server to now use the mechanism as my
core 'construction' model. I'd like to report some
observations:
1. The entire deferred processing chain is quite nice,
although the bulk of the time my callbacks don't take
any arguments. I suspect that most people who do have
callback args can just make an object and pass a bound
method as their callback instead of the 'args' mechanism.
This mechanism could be better supported by providing
a wrapper class instead of maintaining all of the args
throught the code:
class DispatchCallback:
def __init__(self, callback, args, kw):
self.callback = callback
self.args = args or ()
self.kw = kw or {}
def apply(self,result):
return apply(self.callback, (result,)+self.args, kw)
Then, in addCallbacks, if any callback was given which had
args or a kw, you could just construct this dispatch object
and put the ob.apply into the callback stack. In this
way only users who passed in callbacks with args pay for the
penalty of applying them.
2. The error handling could use help. The cross-over behavior,
while unique, doesn't provide any more value over a simple
stack /w state flag (good/bad) attach to each callback.
def _addCallback(self, callback, *args=None, **kw=None, state=good):
if args or kw: callback = DispatchCallback(callback,args,kw)
self.callbacks.append((state,callback))
Of course, existing functions can then be expressed as a
sequence of addCallback /w state flag.
addCallback => _addCallback(self,callback,args,kw,state=good)
addErrback => _addCallback(self,errback,args,kw,state=bad)
addCallbacks => addErrback(...); addCallback(...);
It would have the same behavior since the current cross/over
can easily be denormalized into two entries into the stack, one
for a bad state (first) and then one for a good state (second).
With these changes in place, _runCallbacks() is much simpler...
3. Deferred also "artifically limits" so that the entire callback
tree can only be done once. This involves a hack "MultiDeferred"
to solve the problem; but with a slightly different _runCallbacks
plus a __init__ flag, this need not be the case.
In short, the "multi-call deferred" that I need shouldn't be
a separate class, its behavior can be rolled into the core Deferred
without changing existing behavior (default to callOnceOnly).
4. Lastly, I'd like to see other "state" variables for the deferred.
Essentially, what I see is a process flow, aka state transition
mechnanism emerging. For an SQL query, one of the states
is 'good', 'bad', and 'finished'. Perhaps I'm a bit off here,
but being able to handle more than two states could be useful.
I say this beacuse the MultiDeferred class has a addFinishCallback,
which is fired on the 'finish' state. Note that errors in the
'row' callback and the 'finish' callback could possibly be treated
the same, so there probably isn't a reason to split this... but
maybye not.. I'm still thinking this one out.
Just some thoughts. If you like, I could refactor the Deferred
object as above (it'd be less code & probably cleaner), as well
as updating the documentation and providing examples which run
without modification.
All in all, this is a great concept... and it's working beautyfully
in my current application (mod the minor blemishes above).
Best,
Clark
More information about the Twisted-Python
mailing list