ADVANCED PYTHON PROGRAMMING

Loopin’ Around

This time, we take a look at loops—but do so from a Pythonic point of view, as Guido intended.

Dan Gittik
9 min readApr 6, 2020

--

Having covered conditional statements, it’s time for the next step in control flow: loops. Again, even though it might sound like a basic topic (and it is), there’s a lot to say about it that’s too nuanced for the first time around, and is worth a revision.

Many Loops

There are many different loops out there, so it’s alright to not be proficient in all of them—but it’s less alright to not be proficient in the ones you use. The most common loop by far is the while loop, which goes like that:

However, in other languages you have more flavors—like a do-while loop, which happens at least once; or the repeat-until loop, which happens until the condition is false. Older languages would sometimes simulate loops using conditional statements and simple goto commands, like so:

Funnily enough, for loops were just syntactic sugar for while loops at first—a more compact form for the counter’s boilerplate:

A more interesting version of it would be for-each loops, executing something once for every item in a collection:

Or, in functional languages like Javascript:

Python has two loop forms—the classic while loop, and a for-each loop that is simply called a for loop; so let’s take a closer look at them!

While Loops

There’s not much to be said about while loops, really. I guess we should mention the break keyword, which jumps outside the loop:

And the continue keyword, which skips the rest of the iteration and moves on to the next one:

One myth I’d like to bust is that infinite loops are a bad thing. First, because some tasks are inherently infinite, like an echo server:

Second, because if the loop looks a bit cumbersome, it might be nicer to rephrase with while-true:

For the Loop!

For loops are much more interesting—primarily because people coming from other languages often misuse them. So let’s talk about how too loop, Python style! To iterate over a list of colors, a C or Java developer might write this:

That’s OK, but deals with a lower level abstraction than we’d like: instead “iterate over the colors”, it says “iterate over the numbers from 0 to the length of the colors’ list” and uses it for an index. Why do this, when you can simply:

gnipooL

One reason to work with indexes is if we’d want to iterate over the colors in reverse; then you can do:

But that’s kind of ugly. Why do that, when you can simply:

Counting

But surely if we need a counter, it’s better to use—well, a counter. Right?

But instead, you could simply:

What the built in enumerate function does is take a sequence, and return a sequence of pairs instead—the first slot being the counter, and the second being the item from the original sequence. You can even set the counter’s initial value:

Looooping

What if we want to iterate over both colors and shapes? We’d have to do this:

Or, we could use the built in zip function, which takes any number of sequences, and returns a sequence of tuples—all the first items of all the sequences, then all the second items of all the sequences, and so on, until one of the sequences drains.

What if the lists have different lengths? The zip function stops as soon as one of the lists is over; so we’re back to:

In this case, we don’t have a built in function to save us; but we have one that is part of the standard library!

Both the zip and the zip_longest functions can do more; but it’s pretty technical and boring, so I’ll add an example and leave it at that.

Or Else

In Python, loops can have an else clause, too; and it’s a bit confusing at first. After all, isn’t this…

…Pretty much the same as this?

The truth is—yes and no. To understand it, let’s talk about the weird choice of keyword first: why else? Well, loops are not unlike if statements—you could argue an if statement is a loop with zero or one iterations, whereas a while loop keeps checking the condition over and over again. But then, an else statement is just a piece of code that happens when the condition is not true. In case of an if statement, it means “instead”; but in a while loop, it means “eventually”. When the loop ends, and its condition is no longer true, then comes the else clause’s turn; but isn’t it the same as simply placing the else clause after the loop?

If the loop ends naturally—if it’s “exhausted”, if you will—then it’s the same. But a loop can end unnaturally, because of a break statement; and in this case, it’s condition is still true, so the else statement doesn’t happen. That’s why some people prefer to think of it as a nobreak clause—it happens if, and only if, the loop ended without breaking.

To see why this can be useful, consider the following scenario: we have a list of user objects, and we’re looking for a user with a specific name.

Let’s start with an empty list:

OK, so that’s a bug. We need a default value, in case there are no users:

But this doesn’t work when there are some users in the list!

So it seems we have no choice but to use a flag:

This one actually works—but it’s a lot of code for a pretty simple notion: get the next user with this name, or else. How about:

So it turns out, else clauses actually make life easier sometimes; but at least from my experience, whenever it does—a function would’ve been even better:

else clauses are also the only way to get out of a nested loop in Python, which doesn’t have labels and goto statements. It can wrinkle your brain, but it works:

In this case, we match all the numbers from xs with all the numbers from ys, until the two match—at which point, we break out of the inner loop, but we’re still in the outer one. Luckily, we can hack our way out by detecting the opposite of what we want: whether the inner loop has exhausted naturally. If so, we continue to the next iteration, skipping the last statement—and if not, we reach it and break free. Of course, this would’ve been much simpler:

Sentinels

Another interesting built in to mention is the iter function; it’s used to convert iterable objects, list lists, into iterators—which is what the for loop works with. You don’t have to call it yourself—it’s called implicitly on the for loop’s target, kind of like the bool function is called implicitly on the if statement’s condition.

However, if you do decide to call it explicitly—it accepts a second, optional argument: sentinel. If this argument is provided, then the first argument—the iterable, that is—should be a callable function, which will be re-invoked until it produces a value equal to the sentinel. This allows for yet another looping paradigm: keep producing results, until a certain special value is returned to signal game over. For example, if we’d like to read lines from a file until a ‘stop’ line is reached, we could do this:

Similarly, if we’d like to read from a socket until it closes, we could do this:

But wait! The recv() takes one argument: bufsize, the maximum amount of bytes to read. Luckily, a function can easily be turned into a “thunk”—a deferred execution, that is—by wrapping it up in a lambda:

Just a word for the wise: the sentinel argument is not too common, and if you’re not familiar with this looping paradigm—it looks pretty weird. I think it’s important to know about it, especially if you encounter it in someone else’s code; but it’s like the word “indubitably”—you wouldn’t use it in a sentence yourself.

Conclusion

This time, we talked a lot about loops—and specifically, the two loops available in Python: while and for (well, for-each). We saw how to use them properly, and even mentioned little-known features like zip_longest and sentinels. There was also that strange else clause, and the inevitable conclusion that functions are just better—so if they’re all that great, why don’t we go and marry one in the next post!

The Advanced Python Programming series includes the following articles:

  1. A Value by Any Other Name
  2. To Be, or Not to Be
  3. Loopin’ Around
  4. Functions at Last
  5. To Functions, and Beyond!
  6. Function Internals 1
  7. Function Internals 2
  8. Next Generation
  9. Objects — Objects Everywhere
  10. Objects Incarnate
  11. Meddling with Primal Forces
  12. Descriptors Aplenty
  13. Death and Taxes
  14. Metaphysics
  15. The Ones that Got Away
  16. International Trade

--

--

Dan Gittik

Lecturer at Tel Aviv university. Having worked in Military Intelligence, Google and Magic Leap, I’m passionate about the intersection of theory and practice.