For Statements¶
For statements is my favorite feature of Python. It’s hard to really appreciate why it is so ground-breaking without knowing how other languages solve this problem.
Syntax¶
for <target list> in <expr list>: SUITE
else: SUITE
<target list>
is the same as for the assign statement. Parallel assignment is
OK, and I use it often. We’ll have examples that include this later.
The simplest target is just a single variable name.
<expr list>
is an expression list. You can use star expressions or
whatever you want, as long as the expr list evaluates to an iterable of any
kind.
The for
statement first creates an iterator of the <expr list>
.
Remember that it’s OK to use an iterator, since the iterator of an iterator is
just the iterator.
Then, it calls next()
on the iterator. If it raises StopIteration
because it has exhausted all the items, then it will execute the else
block, and continue with the next statement following the for
statement.
If there is a value, then it assigns the value to the <target list>
as if
it were an assign statement. Then it runs the for
block.
If the for
block executes a continue
, or it finishes, then the
next()
value is grabbed from the iterator, and it continues.
If the for
block executes a break
statement, then the execution of the
for
block is interrupted, the else
block bypassed, and execution
continues with the next statement after the for
statement.
This can be rewritten as a while loop:
i = iter(<expr list>)
while True:
try:
<target list> = next(i)
except StopIteration:
<else block>
break
<for block>
Understanding the Else Block¶
For a while
loop, the else
block is executed when the while
condition evaluates to False
. It is the code that is run as long as
the break
statement is never run.
For a for
loop, the else
block is executed when the <expr list>
runs out of items. It is the code that is run as long as the break
statement is never run.
Although it is quite rare to see an else
block for a while
loop, it is
actually pretty common to see an else
block on a for
loop. For
instance, you might iterate across a sequence, looking for a particular value.
If it is found, your code may break
out of the loop, because it is
pointless to look any further. In this case, the else
block is run if it
is not found.
Example: Simple Iteration¶
Typically, we use for
loops to do something on each item in a sequence. In
this case, we’re going to print out values in a sequence.
items = (1, 4.0, 1+2j, "seventeen")
for i in items:
print(i)
Example: Two-deep Iteration¶
It’s not uncommon to have tuples of tuples. As long as they are simple
nestings without arbitrary depth, we can use nested for
loops to touch
each item. In this case, we want to add two matrices. Note that we iterate
across the indexes of each matrix.
a = (
(0, 1, 4),
(3, 2, 8),
(1, 9, 7),
)
b = (
(1, 2, 1),
(0, 9, 2),
(3, 4, 6),
)
result = ()
for i in range(3):
row = ()
for j in range(3):
row = row + (a[i][j]+b[i][j],)
result = result + (row,)
It might be beneficial to take some time to understand the tuple addition in the last few lines.
Note
Many programmers have a problem with short variable names such as i
and
j
. As long as these are used in the context of iteration of a sequence,
such as in a for
statement, I have never had an issue with it.
enumerate()
¶
If you want to iterate across a sequence and keep track of which index you are
at, then enumerate()
is particularly powerful.
enumerate()
takes an iterable as an argument, and returns an iterator with
tuple pairs. The first is the index of the item, and the second is the item.
Let’s play around with it a bit to understand how it works:
>>> e = enumerate("Hello, World!")
>>> next(e)
(0, 'H')
>>> next(e)
(1, 'e')
>>> next(e)
(2, 'l')
>>> next(e)
(3, 'l')
>>> next(e)
(4, 'o')
>>> next(e)
(5, ',')
>>> next(e)
(6, ' ')
>>> next(e)
(7, 'W')
Used in a for
loop, this is particularly powerful:
for i, char in enumerate("Hello, World!"):
print("The character at", i, "is", char)
You can also set the starting index with the second parameter. I use this if I have already cut up the sequence and want to get back to the original value.
for i, char in enumerate("Hello, World!"[7:], 7):
print("The character at", i, "is", char)
Example: Finding an Item¶
We already have the index()
method to find where a particular element
resides in a tuple, a string, or a bytes object.
What if it isn’t a simple compare we are after?
In this example, we’re looking for the third odd value in a sequence.
values = (1, 2, 6, 5, 9, 11, 12)
odds_seen = 0
for i, value in enumerate(values):
if value % 2 == 1:
odds_seen = odds_seen + 1
if odds_seen == 3: break
Note that the variables i
and value
persist after the for
loop has
completed. (The only time variables don’t persist is in an except
block,
and that’s only because keeping an exception around will prevent garbage
collection of the frames and stack from occurring.)
Let’s turn the above into a pure function. Remember, pure functions don’t rely on global state and don’t modify their arguments.
def find_odds(values, num_odds=1):
odds_seen = 0
for i, value in enumerate(values):
if value % 2 == 1:
odds_seen = odds_seen + 1
if odds_seen == 3: break
return i
We could’ve just as easily returned from inside the for
loop rather than
breaking.
Using Variables After the For
Statement¶
It may be tempting to always use the variables after a for
loop, but this
can be dangerous. If the sequence that is being iterated across is empty, then
the variable will never get assigned.
This leads many programmers to avoid using the variables at all. I think this
is unfortunate, because there are cases where it can be very useful (see the
“Finding an Item” example above.) Just be sure to either check that the
sequence isn’t empty, or catch and process the NameError
exception if it
was never used.
Some people avoid using it out of ignorance of the way Python does things. For instance, in some languages, the variables are not accessible outside of the for loop body.
Limitations of the For Loop¶
The for
loop is not a panacea. It is a convenience for particular cases
where iterating across each item in a sequence and doing something is useful
In particular, if you need to iterate across two loops in parallel, you can’t
use the for
loop unless you are doing something like the matrix example
above. IE, if you want to join two sequences together in sorted order,
grabbing the lowest value from one of the sequences, the for
loop won’t
help you.