A pythonic collection

A while ago I turned to Python as my primary language. I've seen a lot of Python code before, but it was only when I was using it on a daily basis that I started to really appreciate it's beauty. One of the main reasons for this is its consistency. This consistency is what we commonly call pythonic.

Collections are a great example of how this consistency becomes apparent. There are three components at play that make Python collections pythonic:

  • A comprehensive list of built-in collections (list, dict, set, etc...);
  • A set of built-in idioms (len, in, del, etc...);
  • A mechanism for user defined sequences to take advantage of the built-in idioms.

The third component is probably the one that will take longer for new developers to find out. But once they do, writing Python becomes a lot more fun. Also, Python makes this very easy. There is a module called collections.abc (documentation here) that define a set of abstract classes that can be extended in order to guarantee that a user defined collection remains pythonic.

Lets jump to a small example of how this can used in practice. Lets build a small class to that stores a collection of music albums.

import collections  
from collections.abc import MutableSequence

Album = collections.namedtuple("Album", ["name", "artist", "year"])

class AlbumCollection(MutableSequence):  
    def __init__(self, *albums):
        self.inner_list = list()

    def __len__(self):
        return len(self.inner_list)

    def __getitem__(self, index):
        return self.inner_list[index]

    def __delitem__(self, index):
        del self.inner_list[index]

    def insert(self, index, value):
        self.inner_list.insert(index, value)

    def __setitem__(self, index, value):
        self.inner_list[index] = value

We're inheriting from MutableSequence and only implementing the abstract methods for now. These are defined in the documentation, but it can also be useful to take a step ahead and look at the code.

Most of the behavior in our class is just delegating to a the inner_list. But this is enough to illustrate how we can use some of the Python idioms with a custom type.

First, let's create an instance.

>>> album_collection = AlbumCollection(
...   Album("Nevermind", "Nirvana", 1991),
...   Album("OK Computer", "Radiohead", 1997),
...   Album("Siamese Dreams", "The Smashing Pumpkins", 1993),
...   Album("Superunknown", "Soundgarden", 1994)
... )

And see how we can immediately use some of the most popular Python collection idioms in our newly defined data structure.

>>> album_collection[1]
Album(name='OK Computer', artist='Radiohead', year=1997)  
>>> len(album_collection)
>>> in_utero = Album("In Utero", "Nirvana", 1993)
>>> album_collection.append(in_utero)
>>> in_utero in album_collection
>>> album_collection.remove(in_utero)
>>> in_utero in album_collection
>>> album_collection[1:3]
[Album(name='OK Computer', artist='Radiohead', year=1997),
 Album(name='Siamese Dreams', artist='The Smashing Pumpkins', year=1993)]

You get random access to items using the [] notation, there's support for len, in, del, slices and much more.

Most of these idioms are made possible by the so called special methods (aka magic methods) like __len__ and __getitem__. You can learn more about this pattern in the Python Datamodel documentation.

This is only a small introduction but I hope it's representative of how beautifully practical Python can be when we start peaking under the hood.

Note: All the examples and links are for Python 3.5.