The range
call is also
sometimes used to iterate over a sequence indirectly, though
it’s often not the best approach in this role. The easiest and
generally fastest way to step through a sequence exhaustively is
always with a simple for
, as Python handles most of
the details for you:
>>>X = 'spam'
>>>for item in X: print(item, end=' ')
# Simple iteration
... s p a m
Internally, the for
loop handles the details of the
iteration automatically when used this way. If you really need to
take over the indexing logic explicitly, you can do it with a while
loop:
>>>i = 0
>>>while i < len(X):
# while loop iteration
...print(X[i], end=' ')
...i += 1
... s p a m
You can also do manual indexing with a for
, though,
if you use range
to generate a list of indexes to
iterate through. It’s a multistep process, but it’s sufficient to
generate offsets, rather than the items at those offsets:
>>>X
'spam' >>>len(X)
# Length of string
4 >>>list(range(len(X)))
# All legal offsets into X
[0, 1, 2, 3] >>> >>>for i in range(len(X)): print(X[i], end=' ')
# Manual range/len iteration
... s p a m
Note that because this example is stepping over a list of offsets into X
, not the actual items of
X
, we need to index back into X
within
the loop to fetch each item. If this seems like overkill, though,
it’s because it is: there’s really no reason to work this hard in
this example.
Although the range
/len
combination
suffices in this role, it’s probably not the best option. It may
run slower, and it’s also more work than we need to do. Unless you
have a special indexing requirement, you’re better off using the
simple for
loop form in Python:
>>>for item in X: print(item, end=' ')
# Use simple iteration if you can
As a general rule, use for
instead of while
whenever possible, and don’t use range
calls in for
loops except as a last resort. This simpler solution is
almost always better. Like every good rule, though, there are
plenty of exceptions—as the next section demonstrates.
Though not ideal for simple sequence scans, the coding pattern used in the prior example does allow us to do more specialized sorts of traversals when required. For example, some algorithms can make use of sequence reordering—to generate alternatives in searches, to test the effect of different value orderings, and so on. Such cases may require offsets in order to pull sequences apart and put them back together, as in the following; the range’s integers provide a repeat count in the first, and a position for slicing in the second:
>>>S = 'spam'
>>>for i in range(len(S)):
# For repeat counts 0..3
...S = S[1:] + S[:1]
# Move front item to end
...print(S, end=' ')
... pams amsp mspa spam >>>S
'spam' >>>for i in range(len(S)):
# For positions 0..3
...X = S[i:] + S[:i]
# Rear part + front part
...print(X, end=' ')
... spam pams amsp mspa
Trace through these one iteration at a time if they seem confusing. The second creates the same results as the first, though in a different order, and doesn’t change the original variable as it goes. Because both slice to obtain parts to concatenate, they also work on any type of sequence, and return sequences of the same type as that being shuffled—if you shuffle a list, you create reordered lists:
>>>L = [1, 2, 3]
>>>for i in range(len(L)):
...X = L[i:] + L[:i]
# Works on any sequence type
...print(X, end=' ')
... [1, 2, 3] [2, 3, 1] [3, 1, 2]
We’ll make use of code like this to test functions with different argument orderings in Chapter 18, and will extend it to functions, generators, and more complete permutations in Chapter 20—it’s a widely useful tool.
Cases like that of the
prior section are valid applications for the
range
/len
combination. We might also use this technique to skip items as we
go:
>>>S = 'abcdefghijk'
>>>list(range(0, len(S), 2))
[0, 2, 4, 6, 8, 10] >>>for i in range(0, len(S), 2): print(S[i], end=' ')
... a c e g i k
Here, we visit every second item in the string S
by stepping over the generated range
list. To visit
every third item, change the third range
argument to
be 3
, and so on. In effect, using range
this way lets you skip items in loops while still retaining the
simplicity of the for
loop construct.
In most cases, though, this is also probably not the “best
practice” technique in Python today. If you really mean to skip
items in a sequence, the extended three-limit form of the slice
expression, presented in
Chapter 7, provides a simpler route to the same goal. To visit
every second character in S
, for example, slice with a
stride of 2:
>>>S = 'abcdefghijk'
>>>for c in S[::2]:
print(c, end=' ')
... a c e g i k
The result is the same, but substantially easier for you to
write and for others to read. The potential advantage to using
range
here instead is space: slicing makes a copy of
the string in both 2.X and 3.X, while range
in 3.X and
xrange
in 2.X do not create a list; for very large
strings, they may save memory.