Index Iteration: __getitem__

Here’s a hook that isn’t always obvious to beginners, but turns out to be surprisingly useful. In the absence of more-specific iteration methods we’ll get to in the next section, the for statement works by repeatedly indexing a sequence from zero to higher indexes, until an out-of-bounds IndexError exception is detected. Because of that, __getitem__ also turns out to be one way to overload iteration in Python—if this method is defined, for loops call the class’s __getitem__ each time through, with successively higher offsets.

It’s a case of “code one, get one free”—any built-in or user-defined object that responds to indexing also responds to for loop iteration:

>>> class StepperIndex:
        def __getitem__(self, i):
            return self.data[i]

>>> X = StepperIndex()                # X is a StepperIndex object
>>> X.data = "Spam"
>>>
>>> X[1]                              # Indexing calls __getitem__
'p'
>>> for item in X:                    # for loops call __getitem__
        print(item, end=' ')          # for indexes items 0..N

S p a m

In fact, it’s really a case of “code one, get a bunch free.” Any class that supports for loops automatically supports all iteration contexts in Python, many of which we’ve seen in earlier chapters (iteration contexts were presented in Chapter 14). For example, the in membership test, list comprehensions, the map built-in, list and tuple assignments, and type constructors will also call __getitem__ automatically, if it’s defined:

>>> 'p' in X                          # All call __getitem__ too
True

>>> [c for c in X]                    # List comprehension
['S', 'p', 'a', 'm']

>>> list(map(str.upper, X))           # map calls (use list() in 3.X)
['S', 'P', 'A', 'M']

>>> (a, b, c, d) = X                  # Sequence assignments
>>> a, c, d
('S', 'a', 'm')

>>> list(X), tuple(X), ''.join(X)     # And so on...
(['S', 'p', 'a', 'm'], ('S', 'p', 'a', 'm'), 'Spam')

>>> X
<__main__.StepperIndex object at 0x000000000297B630>

In practice, this technique can be used to create objects that provide a sequence interface and to add logic to built-in sequence type operations; we’ll revisit this idea when extending built-in types in Chapter 32.