[Twisted-Python] A Python metaclass for Twisted allowing __init__ to return a Deferred
![](https://secure.gravatar.com/avatar/c8f4d4d1a8fc53c13ed05c202e0257fe.jpg?s=120&d=mm&r=g)
I just posted a blog article with title as above: http://bit.ly/1whZUK Very briefly, I wrote a metaclass to allow you to write classes whose __init__ method uses deferreds. Your __init__ can create deferreds, call functions that return deferreds, and of course return a deferred itself (that's the whole point). Your class instance wont be available until after the deferred your __init__ returns has fired. You use it like this: from txDeferredInitMeta import TxDeferredInitMeta class MyClass(object): __metaclass__ = TxDeferredInitMeta def __init__(self): d = aFuncReturningADeferred() return d def cb((instance, result)): # instance is an instance of MyClass # result is from the callback chain of aFuncReturningADeferred pass d = MyClass() d.__instantiate__() d.addCallback(cb) Metaclass code & test suite at http://foss.fluidinfo.com/txDeferredInitMeta.zip For more details on how it works see http://bit.ly/1whZUK I'll be happy to explain why this is useful if anyone cares. Or maybe there's a better way to do this that I don't know about. I always have the feeling that I know just the barest amount about what's out there in the Twisted codebase, and that every problem I run into must have already been encountered by many and solved in some insanely elegant and general way. In any case, I had fun. Terry
![](https://secure.gravatar.com/avatar/7ed9784cbb1ba1ef75454034b3a8e6a1.jpg?s=120&d=mm&r=g)
On Mon, 3 Nov 2008 00:48:20 +0100, Terry Jones <terry@jon.es> wrote:
I just posted a blog article with title as above: http://bit.ly/1whZUK
Very briefly, I wrote a metaclass to allow you to write classes whose __init__ method uses deferreds. Your __init__ can create deferreds, call functions that return deferreds, and of course return a deferred itself (that's the whole point). Your class instance wont be available until after the deferred your __init__ returns has fired.
You use it like this:
from txDeferredInitMeta import TxDeferredInitMeta
class MyClass(object): __metaclass__ = TxDeferredInitMeta def __init__(self): d = aFuncReturningADeferred() return d
def cb((instance, result)): # instance is an instance of MyClass # result is from the callback chain of aFuncReturningADeferred pass
d = MyClass() d.__instantiate__() d.addCallback(cb)
Metaclass code & test suite at http://foss.fluidinfo.com/txDeferredInitMeta.zip
For more details on how it works see http://bit.ly/1whZUK
I'll be happy to explain why this is useful if anyone cares. Or maybe there's a better way to do this that I don't know about. I always have the feeling that I know just the barest amount about what's out there in the Twisted codebase, and that every problem I run into must have already been encountered by many and solved in some insanely elegant and general way. In any case, I had fun.
I usually solve this kind of problem like this: d = aFuncReturningADeferred() d.addCallback(MyClass) d.addCallback(cb) I'll not try to claim anything about the level of elegance, though. :) For less surprisiness, I'd suggest that you at least restrict the result of the Deferred returned from __init__ to firing with None or self and make the argument to the first callback on the Deferred returned by MyClass() just be the new instance. Jean-Paul
![](https://secure.gravatar.com/avatar/a317a8f80c2fc9e5df8470c599e89e2c.jpg?s=120&d=mm&r=g)
On Monday 03 November 2008 00:48:20 Terry Jones wrote:
I just posted a blog article with title as above: http://bit.ly/1whZUK
I'm going to jump in. How about this: from twisted.internet import defer, reactor def aFuncReturningADeferred(value): d = defer.Deferred() reactor.callLater(5, d.callback, value[::-1]) return d class Foo(object): def __new__(cls, value): def cb(x): obj = object.__new__(cls, x) obj.__init__(x) return obj return aFuncReturningADeferred(value).addCallback(cb) def __init__(self, value): self.value = value def printFoo(obj): print obj.value reactor.stop() d = Foo("Some value") d.addCallback(printFoo) reactor.run() Instead of returning a deferred from the __init__ method (which is non-standard), you return it from __new__ It's clearer, since __init__ is meant for configuring instances, and __new__ for instantiating objects. Cheers.
![](https://secure.gravatar.com/avatar/d6328babd9f9a98ecc905e1ccac2495e.jpg?s=120&d=mm&r=g)
On 2 Nov, 11:48 pm, terry@jon.es wrote:
Very briefly, I wrote a metaclass to allow you to write classes whose __init__ method uses deferreds. Your __init__ can create deferreds, call functions that return deferreds, and of course return a deferred itself (that's the whole point). Your class instance wont be available until after the deferred your __init__ returns has fired.
As a general stylistic thing, I've been writing a lot more classmethods lately to determine arguments to __init__, rather than trying to make __init__ itself do interesting tricks. I can't find a name for this "design pattern", so let me describe it: One very common use-case is that we have some object - let's say an RFC 5322 email address - which is typically created from a string. An idiomatic way to do that might be like this: import rfc822 class Address: def __init__(self, addrstr): l = list(rfc822.AddressList(addrstr)) if len(l) != 1: raise ValueError("Too many or too few addresses.") else: desc, addr = l[0] self.description = desc self.localpart, self.domain = addr.split("@") But this is problematic. With this class, it's hard to convert from a different format of storing email addresses that has already been parsed. In order to create an Address from, i.e., a database record containing a description, localpart, and domain, I now need to smash everything back into a string, worrying about trivia like quoting; or I need to resort to hacks like calling __new__ instead of __init__. It makes testing more difficult: in my tests I need to start having formatted email addresses in strings instead of simply creating Address objects. If this class were hypothetically a bit smarter and dealt nicely with unicode, my tests would need to learn about email-address quoting rules in order to generate addresses with non-ASCII characters, rather than leaving that logic entirely in the Address class. Ugly all around. However, I can pull the parsing logic out and separate it from the initialization logic, and all of that gets much easier: class Address: def __init__(self, localpart, domain, description): self.localpart = localpart self.domain = domain self.description = description @classmethod def fromString(cls, addrstr): l = list(rfc822.AddressList(addrstr)) if len(l) != 1: raise ValueError("Too many or too few addresses.") else: desc, addr = l[0] loc, dom = addr.split("@") return cls(loc, dom, desc) With this improved class, I can easily create Address objects in other ways from other code. Since it's a classmethod rather than a function, it's just as friendly to inheritance as a constructor; perhaps even moreso. It opens the door to the evolution of other creation methods, fromXXX classmethods, without breaking the constructor's signature or changing the fromString method. You don't give a concrete example in your blog post, but I can imagine that all these points apply twice over to any code that would use Deferreds. An __init__ that returns a Deferred means that in the testing case, not only is there no way to directly construct the object you want, there might be no way to even get one without spinning the reactor. What is that Deferred doing? Maybe there's no way to get one without actually generating network traffic! Obviously, not an ideal scenario. For the tests for the code making the deferred request itself, there will obviously need to be fake sources of data, but for other tests that just want to interact with one of your objects, direct construction is pretty much always easier. However, thanks for sharing nonetheless. Although I wouldn't use it personally, your code makes an interesting rhetorical point. There's a great deal of whinging that goes on around Deferreds being hard to work with. This metaclass is just another in a long line of tools that says "see? it really isn't so hard to deal with a Deferred if you need to."
participants (4)
-
Esteve Fernandez
-
glyph@divmod.com
-
Jean-Paul Calderone
-
Terry Jones