On May 14, 2009, at 4:01 AM, Terry Jones wrote:
"Phil" == Phil Christensen <[email protected]> writes:

Phil> I don't know if I agree with the need for such a feature (that is,
Phil> deferred __init__ usage), but it was a very interesting coding
Phil> challenge I wanted to take a whack at. I *think* I might have found a
Phil> solution, but I don't know if it falls under the heading of
Phil> "decorator abuse" ;-)

Hi Phil

I finally had time to look at your solution a bit (though I've not run
it). It does a couple of things I wouldn't have thought of, like putting
the dictionary onto the deferredInit function. A couple of comments,
supposing I understand your code properly:

- One thing I had hoped to avoid was to slow the class methods down by
having them always check the original deferred (or a flag) before taking action. My approach does this by moving them aside and then putting them back in place once the deferred fires. Your solution requires that every decorated method does several extra things before it gets going. That could be greatly reduced if you were to check self.initDeferred.called
  and simply call the original function if the deferred has fired.

Yeah, I see what you mean. I changed it to directly call the function if the initDeferred has already been fired. One catch is that I think it's important that the function always return a Deferred, even if it's just a succeed() wrapper, so as to provide a consistent interface whether __init__ is finished or not.

I added a couple additional calls to my test example to illustrate this.

- If multiple calls are made to instance methods before the init deferred
  has fired, they will, as I read it, all try to del
deferredInit.waiting[self] in _finish. So I guess that del needs to be
  conditional or in a try/except.

I realized there's no reason to keep a dictionary anyways, since you always have access to `self`. The result means less bookkeeping, which is always good...

- Using self as a key into the dict on initDeferred seems like it
  addresses Glyph's observation/criticism that my approach raises
  questions wrt inheritance.

I believe even though I removed that state dictionary, this should still work properly, since we always operate on/with `self`.

- You could use chainDeferred where you're currently using
  .addCallbacks(resultDeferred.callback, resultDeferred.errback)

Ah yes. I forgot this existed; a lot of my Deferred experience is with sequential processes, so I've been using inlineCallbacks for everything.

That's all for now. I'll see if I have more time to think about all this. When I tried to use a decorator the first time, I was also using a super
class (whereas you're putting state into a dict on the deferredInit
function) but I got into a mess accessing self properly (partly because, I
think, I wanted to have a mixin class and I was looking at
self.__class__.__mro__).

In any case, thanks for replying, for playing with it, and for posting your
code. I got to learn new things as a result, which is really great :-)

Yeah, same here. It's pretty rare that I can get into any 'semi- advanced' discussions on here (things often seem to go between one extreme and the other), but I always learn lots when I do.

I'm still not sure if I would use this technique myself, but I'm reasonably satisfied with the "scent" of this code. Obviously though, I haven't thought through a whole bunch of use cases, but it seems to be pretty simple, in the end.

I've attached the revised version of the decorator. Let me know if you think of anything else.

Thanks,

-phil

from twisted.internet import defer, reactor
from twisted.enterprise import adbapi

def deferredInit(deferredName):
    def _deferredInit(func):
        def __deferredInit(self, *args, **kwargs):
            initDeferred = None
            if(hasattr(self, deferredName)):
                initDeferred = getattr(self, deferredName)
                if(initDeferred.called):
                    return defer.maybeDeferred(func, self, *args, **kwargs)
            else:
                raise RuntimeError("%s doesn't define the Deferred attribute `%s`." % (self.__class__.__name__, deferredName))
        
            def _finish(result):
                return func(self, *args, **kwargs)
            
            def _finish_error(failure):
                print '_finish_err: %s' % failure
            
            resultDeferred = defer.Deferred()
            resultDeferred.addCallbacks(_finish, _finish_error)
            
            initDeferred.chainDeferred(resultDeferred)
            
            return resultDeferred
        return __deferredInit
    
    if(callable(deferredName)):
        func = deferredName
        deferredName = 'initDeferred'
        return _deferredInit(func)
    
    return _deferredInit

class TestDeferredInit(object):
    def __init__(self):
        self.pool = adbapi.ConnectionPool("MySQLdb", 'localhost', 'modu', 'modu')
        self.initDeferred = self.pool.runQuery("SELECT 'it worked';")
        def _finish_init(msg):
            self.msg = msg
        def _finish_init_error(failure):
            print '_finish_init_err: %s' % failure
        self.initDeferred.addCallbacks(_finish_init, _finish_init_error)
    
    @deferredInit
    def query(self):
        return self.msg

if(__name__ == '__main__'):
    def _print(msg, label):
        print '%s: %s' % (label, msg)
        if(label == '3'):
            reactor.stop()
    
    def _print_error(failure):
        print '_print_err: %s' % failure
    
    test = TestDeferredInit()
    
    d = test.query()
    d.addCallbacks(_print, _print_error, callbackArgs=['1'])
    
    d2 = test.query()
    d2.addCallbacks(_print, _print_error, callbackArgs=['2'])
    
    d3 = test.query()
    d3.addCallbacks(_print, _print_error, callbackArgs=['3'])
    
    reactor.run()

_______________________________________________
Twisted-Python mailing list
[email protected]
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python

Reply via email to