Unified types and classes in Python are one of its most powerful features


Around Python 2.2, before I had started to code in Python, PEP 252 and 253 paved the way for what I consider some of Python's most powerful features. There are a few issues I'd like to focus on the first of those is the ability subclass python's base types.

So say we need a dictionary that when a key is set to a value on that dictionary the key is appended to a list instead of overriding the old value. This is useful in for example HTTP headers where things like Set-Cookie will appear multiple times but with different values. We have a few options to go with here. We can roll our own class which implements the same methods as dict and so would thus work just like a dict (aside from the changes we want). So what exactly would have to be implemented to make that possible on a reasonably usable level; we'll need: __getitem__, __repr__, __setitem__, __delitem__, items, iteritems, values, itervalues, keys, iterkeys, __contains__, and get. I'd say that would provide a reasonably usable and introspectively capable implementation of dict which we could adapt for our purposes. That's a lot of code that acts just like dict. So we have another option. We can use UserDict which is basically dict reimplemented so it can be inherited from which is back from the days before the great unification. So that brings us to the awesome approach that is provided by the grand unification. We can just inherit from dict and override the parts we need to change. Our example can look something like:

class ResponseHeaders(dict):
    def __setitem__(self, item, val):
        if item in self:
            iv = self[item]
            if isinstance(iv, list):
                iv.append(val)
            else:
                iv = [iv, val]
                super(ResponseHeader, self).__setitem__(item, iv)
        else:
            super(ResponseHeader, self).__setitem__(item, val)

    def items(self):
        ret = []
        for k,v in super(ResponseHeader, self).items():
            if isinstance(v, list):
                ret.extend([ (k, a) for a in v ])
            else:
                ret.append((k, v))
        return ret

    def iteritems(self):
        return iter(self.items())

That's a really cool feature that makes my life a lot easier in that if I'm using a dict to store things and I pretty much need a dict, I can adapt the dict to be just right instead of writing a new kind of object. The great unification had some other really cool side-effects though. It's now possible to override __new__ which is the method used to actually create the object instance in a class. A classic reason you might need this is for the Singleton pattern. You should of course remember the reasons not to use the Singleton. Anyway, it's good as an exercise to show off new:

class Singleton(object):
    _single = None
    def __new__(typ, *args, *kwargs):
        if not typ._single:
            typ._single = object.__new__(typ, *args, **kwargs)
        return typ._single

It might be better to directly call the object's __new__ method instead of calling object's __new__ method indirectly, but the power of being able to override __new__ is still quite clear.

I use this capability a lot. TagFu is built around overriding dict and list so that they read and write from a sqlite database instead of in memory storage. Combined with Python's expressiveness the capability of python's native types and the ease of overriding and adapting them are why I am such an avid Python programmer.


published at Tue Apr 10 15:45:20 2007 (-0700) by alexbl
Tags: python, dict, list
| |