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."