Collections

Membership

To overload the in operator you can define the object.__contains__() method:

class PrimaryColors:
    def __init__(self, color1, color2, color3):
        self.color1 = color1
        self.color2 = color2
        self.color3 = color3

    def __contains__(self, color):
        return color in (self.color1, self.color2, self.color3)

crt = PrimaryColors('red', 'green', 'blue')

assert 'red' in crt
assert 'cyan' not in crt

Indexing and Slicing

These methods are useful for implementing collections similar to lists and dictionaries:

class Indexable:
    def __getitem__(self, key):
        print '__getitem__ called with key', repr(key)

    def __setitem__(self, key, value):
        print '__setitem__ called with key', repr(key), 'and value', repr(value)

    def __delitem__(self, key):
        print '__delitem__ called with key', repr(key)

a = Indexable()

You can use anything as a key:

a[0]
a['test']
a[set([1, 2])]

The slicing syntax creates slice() objects:

a[1:2]
a[1:10:2]
a[:3]

Of course, you can call the slice() function yourself:

a[slice(1, 2)]
a[slice(1, 10, 2)]
a[slice(0, 3)]

The methods object.__setitem__() and object.__delitem__() receive the same key argument as object.__getitem__():

a[0] = 3
a[1:3] = [1]
del a[0]
del a[1:3]

If the class does not support the iteration protocol, object.__getitem__() will be called with integer arguments starting from 0 until the function raises exceptions.IndexError:

class Cubes:
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key):
        if key >= 0 and key < self.n:
            return key * key * key
        else:
            raise IndexError

assert [x for x in Cubes(10)] == [0, 1, 8, 27, 64, 125, 216, 343, 512, 729]

Exercise

Implement a dictionary that remembers the order in which the keys were inserted (see the unit test). The class must start with:

class OrderedDict:
    def __init__(self):
        self._keys = []
        self._dict = {}

Notes:

  • when defining a custom dictionary class you would normally inherit from dict and get some operators for free, but the purpose of this exercise is to implement everything manually

  • a dictionary that remembers the insertion order is included in the standard library: collections.OrderedDict

  • two dictionaries are considered equal if they have the same keys (regardless of order) and the values for each key are equal.

Unit test:

def test_ordereddict():
    od = OrderedDict()

    # __setitem__
    od['color'] = 'black'
    od['price'] = 99
    od['delay'] = 60

    # __len__
    assert len(od) == 3

    # __contains__
    assert 'color' in od

    # __getitem__
    assert od['color'] == 'black'

    # keys() in insertion order
    assert od.keys() == ['color', 'price', 'delay']

    # __iter__
    assert list(od) == ['color', 'price', 'delay']

    # iteritems() in insertion order
    assert list(od.iteritems()) == \
           [('color', 'black'), ('price', 99), ('delay', 60)]

    # __repr__
    assert repr(od) == "{'color': 'black', 'price': 99, 'delay': 60}"

    # __delitem__
    del od['delay']

    # __eq__
    assert od == {'color': 'black', 'price': 99}

    # __ne__
    assert od != {'color': 'black', 'price': 150}
    assert not od != {'color': 'black', 'price': 99}