Boring is good in Python’s __init__

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

Interesting:

You might notice that these three observations I’ve made all sound a bit negative. You might conclude that I think this is an antipattern to be avoided. If so, feel free to give yourself a pat on the back at this point.

But if this is an antipattern, is there a pattern to use instead? I think so. I’ll try to explain it.

The general idea behind the pattern I’m going to suggest comes in two parts. The first part is that your object should primarily be about representing state and your __init__ method should be about accepting that state from the outside world and storing it away on the instance being initialized for later use. It should always represent complete, internally consistent state – not partial state as asynchronous initialization implies. This means your __init__ methods should mostly look like this:

class Microblog(object):
    def __init__(self, cache_dir, database_connection):
        self.cache_dir = cache_dir
        self.database_connection = database_connection

If you think that looks boring – yes, it does. Boring is a good thing here. Anything exciting your __init__ method does is probably going to be the cause of someone’s bad day sooner or later. If you think it looks tedious – yes, it does. Consider using Hynek Schlawack’s excellent characteristic package (full disclosure – I contributed some ideas to characteristic’s design and Hynek ocassionally says nice things about me (I don’t know if he means them, I just know he says them)).

The second part of the idea an acknowledgement that asynchronous initialization is a reality of programming with asynchronous tools. Fortunately __init__ isn’t the only place to put code. Asynchronous factory functions are a great way to wrap up the asynchronous work sometimes necessary before an object can be fully and consistently initialized. Put another way:

class Microblog(object):
    # ... __init__ as above ...

    @classmethod
    @asyncio.coroutine
    def from_database(cls, cache_dir, database_path):
        # ... or make it a free function, not a classmethod, if you prefer
        loop = asyncio.get_event_loop()
        database_connection = yield from loop.run_in_executor(None, cls._reading_init)
        return cls(cache_dir, database_connection)

Notice that the setup work for a Microblog instance is still asynchronous but initialization of the Microblog instance is not. There is never a time when a Microblog instance is hanging around partially ready for action. There is setup work and then there is a complete, usable Microblog.

Post external references

  1. 1
    http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html
Source