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.