In the preceding chapter, I mentioned that the for loop can work on any sequence type in Python, including lists, tuples, and strings, like this:
>>>for x in [1, 2, 3, 4]: print(x ** 2, end=' ')# In 2.X: print x ** 2, ... 1 4 9 16 >>>for x in (1, 2, 3, 4): print(x ** 3, end=' ')... 1 8 27 64 >>>for x in 'spam': print(x * 2, end=' ')... ss pp aa mm
Actually, the for loop turns out to be even more generic than this—it works on any iterable object. In fact, this is true of all iteration tools that scan objects from left to right in Python, including for loops, the list comprehensions we’ll study in this chapter, in membership tests, the map built-in function, and more.
The concept of “iterable objects” is relatively recent in Python, but it has come to permeate the language’s design. It’s essentially a generalization of the notion of sequences—an object is considered iterable if it is either a physically stored sequence, or an object that produces one result at a time in the context of an iteration tool like a for loop. In a sense, iterable objects include both physical sequences and virtual sequences computed on demand.
Terminology in this topic tends to be a bit loose. The terms “iterable” and “iterator” are sometimes used interchangeably to refer to an object that supports iteration in general. For clarity, this book has a very strong preference for using the term iterable to refer to an object that supports the iter call, and iterator to refer to an object returned by an iterable on iter that supports the next(I) call. Both these calls are defined ahead.
That convention is not universal in either the Python world or this book, though; “iterator” is also sometimes used for tools that iterate. Chapter 20 extends this category with the term “generator”—which refers to objects that automatically support the iteration protocol, and hence are iterable—even though all iterables generate results!
One of the easiest ways to understand the iteration protocol is to see how it works with a built-in type such as the file. In this chapter, we’ll be using the following input file to demonstrate:
>>>print(open('script2.py').read())import sys print(sys.path) x = 2 print(x ** 32) >>>open('script2.py').read()'import sys\nprint(sys.path)\nx = 2\nprint(x ** 32)\n'
Recall from Chapter 9 that open file objects have a method called readline, which reads one line of text from a file at a time—each time we call the readline method, we advance to the next line. At the end of the file, an empty string is returned, which we can detect to break out of the loop:
>>>f = open('script2.py')# Read a four-line script file in this directory >>>f.readline()# readline loads one line on each call 'import sys\n' >>>f.readline()'print(sys.path)\n' >>>f.readline()'x = 2\n' >>>f.readline()# Last lines may have a \n or not 'print(x ** 32)\n' >>>f.readline()# Returns empty string at end-of-file ''
However, files also have a method named __next__ in 3.X (and next in 2.X) that has a nearly identical effect—it returns the next line from a file each time it is called. The only noticeable difference is that __next__ raises a built-in StopIteration exception at end-of-file instead of returning an empty string:
>>>f = open('script2.py')# __next__ loads one line on each call too >>>f.__next__()# But raises an exception at end-of-file 'import sys\n' >>>f.__next__()# Use f.next() in 2.X, or next(f) in 2.X or 3.X 'print(sys.path)\n' >>>f.__next__()'x = 2\n' >>>f.__next__()'print(x ** 32)\n' >>>f.__next__()Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
This interface is most of what we call the iteration protocol in Python. Any object with a __next__ method to advance to a next result, which raises StopIteration at the end of the series of results, is considered an iterator in Python. Any such object may also be stepped through with a for loop or other iteration tool, because all iteration tools normally work internally by calling __next__ on each iteration and catching the StopIteration exception to determine when to exit. As we’ll see in a moment, for some objects the full protocol includes an additional first step to call iter, but this isn’t required for files.
The net effect of this magic is that, as mentioned in Chapter 9 and Chapter 13, the best way to read a text file line by line today is to not read it at all—instead, allow the for loop to automatically call __next__ to advance to the next line on each iteration. The file object’s iterator will do the work of automatically loading lines as you go. The following, for example, reads a file line by line, printing the uppercase version of each line along the way, without ever explicitly reading from the file at all:
>>>for line in open('script2.py'):# Use file iterators to read by lines ...print(line.upper(), end='')# Calls __next__, catches StopIteration ... IMPORT SYS PRINT(SYS.PATH) X = 2 PRINT(X ** 32)
Notice that the print uses end='' here to suppress adding a \n, because line strings already have one (without this, our output would be double-spaced; in 2.X, a trailing comma works the same as the end). This is considered the best way to read text files line by line today, for three reasons: it’s the simplest to code, might be the quickest to run, and is the best in terms of memory usage. The older, original way to achieve the same effect with a for loop is to call the file readlines method to load the file’s content into memory as a list of line strings:
>>>for line in open('script2.py').readlines():...print(line.upper(), end='')... IMPORT SYS PRINT(SYS.PATH) X = 2 PRINT(X ** 32)
This readlines technique still works but is not considered the best practice today and performs poorly in terms of memory usage. In fact, because this version really does load the entire file into memory all at once, it will not even work for files too big to fit into the memory space available on your computer. By contrast, because it reads one line at a time, the iterator-based version is immune to such memory-explosion issues. The iterator version might run quicker too, though this can vary per release
As mentioned in the prior chapter’s sidebar, Why You Will Care: File Scanners, it’s also possible to read a file line by line with a while loop:
>>>f = open('script2.py')>>>while True:...line = f.readline()...if not line: break...print(line.upper(), end='')... ...same output...
However, this may run slower than the iterator-based for loop version, because iterators run at C language speed inside Python, whereas the while loop version runs Python byte code through the Python virtual machine. Anytime we trade Python code for C code, speed tends to increase. This is not an absolute truth, though, especially in Python 3.X; we’ll see timing techniques later in Chapter 21 for measuring the relative speed of alternatives like these.[30]
Version skew note: In Python 2.X, the iteration method is named X.next() instead of X.__next__(). For portability, a next(X) built-in function is also available in both Python 3.X and 2.X (2.6 and later), and calls X.__next__() in 3.X and X.next() in 2.X. Apart from method names, iteration works the same in 2.X and 3.X in all other ways. In 2.6 and 2.7, simply use X.next() or next(X) for manual iterations instead of 3.X’s X.__next__(); prior to 2.6, use X.next() calls instead of next(X).
To simplify manual iteration code, Python 3.X also provides a built-in function, next, that automatically calls an object’s __next__ method. Per the preceding note, this call also is supported on Python 2.X for portability. Given an iterator object X, the call next(X) is the same as X.__next__() on 3.X (and X.next() on 2.X), but is noticeably simpler and more version-neutral. With files, for instance, either form may be used:
>>>f = open('script2.py')>>>f.__next__()# Call iteration method directly 'import sys\n' >>>f.__next__()'print(sys.path)\n' >>>f = open('script2.py')>>>next(f)# The next(f) built-in calls f.__next__() in 3.X 'import sys\n' >>>next(f)# next(f) => [3.X: f.__next__()], [2.X: f.next()] 'print(sys.path)\n'
Technically, there is one more piece to the iteration protocol alluded to earlier. When the for loop begins, it first obtains an iterator from the iterable object by passing it to the iter built-in function; the object returned by iter in turn has the required next method. The iter function internally runs the __iter__ method, much like next and __next__.
As a more formal definition, Figure 14-1 sketches this full iteration protocol, used by every iteration tool in Python, and supported by a wide variety of object types. It’s really based on two objects, used in two distinct steps by iteration tools:
These steps are orchestrated automatically by iteration tools in most cases, but it helps to understand these two objects’ roles. For example, in some cases these two objects are the same when only a single scan is supported (e.g., files), and the iterator object is often temporary, used internally by the iteration tool.
Moreover, some objects are both an iteration context tool (they iterate) and an iterable object (their results are iterable)—including Chapter 20’s generator expressions, and map and zip in Python 3.X. As we’ll see ahead, more tools become iterables in 3.X—including map, zip, range, and some dictionary methods—to avoid constructing result lists in memory all at once.
Figure 14-1. The Python iteration protocol, used by for loops, comprehensions, maps, and more, and supported by files, lists, dictionaries, Chapter 20’s generators, and more. Some objects are both iteration context and iterable object, such as generator expressions and 3.X’s flavors of some tools (such as map and zip). Some objects are both iterable and iterator, returning themselves for the iter() call, which is then a no-op.
In actual code, the protocol’s first step becomes obvious if we look at how for loops internally process built-in sequence types such as lists:
>>>L = [1, 2, 3]>>>I = iter(L)# Obtain an iterator object from an iterable >>>I.__next__()# Call iterator's next to advance to next item 1 >>>I.__next__()# Or use I.next() in 2.X, next(I) in either line 2 >>>I.__next__()3 >>>I.__next__()...error text omitted...StopIteration
This initial step is not required for files, because a file object is its own iterator. Because they support just one iteration (they can’t seek backward to support multiple active scans), files have their own __next__ method and do not need to return a different object that does:
>>>f = open('script2.py')>>>iter(f) is fTrue >>>iter(f) is f.__iter__()True >>>f.__next__()'import sys\n'
Lists and many other built-in objects, though, are not their own iterators because they do support multiple open iterations—for example, there may be multiple iterations in nested loops all at different positions. For such objects, we must call iter to start iterating:
>>>L = [1, 2, 3]>>>iter(L) is LFalse >>>L.__next__()AttributeError: 'list' object has no attribute '__next__' >>>I = iter(L)>>>I.__next__()1 >>>next(I)# Same as I.__next__() 2
Although Python iteration tools call these functions automatically, we can use them to apply the iteration protocol manually, too. The following interaction demonstrates the equivalence between automatic and manual iteration:[31]
>>>L = [1, 2, 3]>>> >>>for X in L:# Automatic iteration ...print(X ** 2, end=' ')# Obtains iter, calls __next__, catches exceptions ... 1 4 9 >>>I = iter(L)# Manual iteration: what for loops usually do >>>while True:...try:# try statement catches exceptions ...X = next(I)# Or call I.__next__ in 3.X ...except StopIteration:...break...print(X ** 2, end=' ')... 1 4 9
To understand this code, you need to know that try statements run an action and catch exceptions that occur while the action runs (we met exceptions briefly in Chapter 11 but will explore them in depth in Part VII). I should also note that for loops and other iteration contexts can sometimes work differently for user-defined classes, repeatedly indexing an object instead of running the iteration protocol, but prefer the iteration protocol if it’s used. We’ll defer that story until we study class operator overloading in Chapter 30.
Besides files and physical sequences like lists, other types have useful iterators as well. The classic way to step through the keys of a dictionary, for example, is to request its keys list explicitly:
>>>D = {'a':1, 'b':2, 'c':3}>>>for key in D.keys():...print(key, D[key])... a 1 b 2 c 3
In recent versions of Python, though, dictionaries are iterables with an iterator that automatically returns one key at a time in an iteration context:
>>>I = iter(D)>>>next(I)'a' >>>next(I)'b' >>>next(I)'c' >>>next(I)Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
The net effect is that we no longer need to call the keys method to step through dictionary keys—the for loop will use the iteration protocol to grab one key each time through:
>>>for key in D:...print(key, D[key])... a 1 b 2 c 3
We can’t delve into their details here, but other Python object types also support the iteration protocol and thus may be used in for loops too. For instance, shelves (an access-by-key filesystem for Python objects) and the results from os.popen (a tool for reading the output of shell commands, which we met in the preceding chapter) are iterable as well:
>>>import os>>>P = os.popen('dir')>>>P.__next__()' Volume in drive C has no label.\n' >>>P.__next__()' Volume Serial Number is D093-D1F7\n' >>>next(P)TypeError: _wrap_close object is not an iterator
Notice that popen objects themselves support a P.next() method in Python 2.X. In 3.X, they support the P.__next__() method, but not the next(P) built-in. Since the latter is defined to call the former, this may seem unusual, though both calls work correctly if we use the full iteration protocol employed automatically by for loops and other iteration contexts, with its top-level iter call (this performs internal steps required to also support next calls for this object):
>>>P = os.popen('dir')>>>I = iter(P)>>>next(I)' Volume in drive C has no label.\n' >>>I.__next__()' Volume Serial Number is D093-D1F7\n'
Also in the systems domain, the standard directory walker in Python, os.walk, is similarly iterable, but we’ll save an example until Chapter 20’s coverage of this tool’s basis—generators and yield.
The iteration protocol also is the reason that we’ve had to wrap some results in a list call to see their values all at once. Objects that are iterable return results one at a time, not in a physical list:
>>>R = range(5)>>>R# Ranges are iterables in 3.X range(0, 5) >>>I = iter(R)# Use iteration protocol to produce results >>>next(I)0 >>>next(I)1 >>>list(range(5))# Or use list to collect all results at once [0, 1, 2, 3, 4]
Note that the list call here is not required in 2.X (where range builds a real list), and is not needed in 3.X for contexts where iteration happens automatically (such as within for loops). It is needed for displaying values here in 3.X, though, and may also be required when list-like behavior or multiple scans are required for objects that produce results on demand in 2.X or 3.X (more on this ahead).
Now that you have a better understanding of this protocol, you should be able to see how it explains why the enumerate tool introduced in the prior chapter works the way it does:
>>>E = enumerate('spam')# enumerate is an iterable too >>>E<enumerate object at 0x00000000029B7678> >>>I = iter(E)>>>next(I)# Generate results with iteration protocol (0, 's') >>>next(I)# Or use list to force generation to run (1, 'p') >>>list(enumerate('spam'))[(0, 's'), (1, 'p'), (2, 'a'), (3, 'm')]
We don’t normally see this machinery because for loops run it for us automatically to step through results. In fact, everything that scans left to right in Python employs the iteration protocol in the same way—including the topic of the next section.
[30] Spoiler alert: the file iterator still appears to be slightly faster than readlines and at least 30% faster than the while loop in both 2.7 and 3.3 on tests I’ve run with this chapter’s code on a 1,000-line file (while is twice as slow on 2.7). The usual benchmarking caveats apply—this is true only for my Pythons, my computer, and my test file, and Python 3.X complicates such analyses by rewriting I/O libraries to support Unicode text and be less system-dependent. Chapter 21 covers tools and techniques you can use to time these loop statements on your own.
[31] Technically speaking, the for loop calls the internal equivalent of I.__next__, instead of the next(I) used here, though there is rarely any difference between the two. Your manual iterations can generally use either call scheme.