As this series grows, I often find myself revisiting the fundamentals. For instance, today we’ll be learning how to write a loop in Python. Luckily for you, there’s some bonus material on recursion as well.
In short, there are two core ways of writing a loop, while
and for
. If you’re looking for a tradition loop, opt for the while
loop. Meanwhile, if you have some sequence or iterable to traverse, opt for the for
loop. If you find a scenario which gets messy with a loop (e.g. tree traversal), don’t be afraid to fall back on recursion.
Table of Contents
Problem Description
When you first get into programming, you often go through a progression of different pieces of syntax. For instance, you might learn about printing and variables. Then, you might expand your knowledge into arithmetic and boolean expressions. If all goes well, you might even learn about conditionals.
As time goes on, you might ask yourself “but, what if I want to do something repeatedly?” Luckily, most imperative programming languages have a syntax for this called looping. Essentially, we repeat a task until we satisfy some condition.
Of course, if you’ve come from another programming language, you already know all about looping (or at least recursion). The trouble is getting used to the new syntax. Fortunately, we have several different solutions which we’ll take a look at in the next section.
Solutions
In this section, we’ll take a look at three different ways to write a loop in Python. First, we’ll look at recursion, a functional technique. Then, we’ll dive into the two iterative techniques, while
and for
.
Recursion
Before we dig into the various loop syntax in Python, I feel like it’s important to mention recursion as a concept. After all, we don’t actually need loops at all. We can get away from writing functions which reference themselves:
def recurse(): recurse()
In this example, we’ve written a function called recurse()
which calls itself. If we run it, however, we’ll get an error:
>>> recurse() Traceback (most recent call last): File "<pyshell#2>", line 1, in <module> recurse() File "<pyshell#1>", line 2, in recurse recurse() File "<pyshell#1>", line 2, in recurse recurse() File "<pyshell#1>", line 2, in recurse recurse() [Previous line repeated 991 more times] RecursionError: maximum recursion depth exceeded
Of course, this makes sense. After all, if a function calls itself, then it will call itself, then it will call itself, then it will call itself… alright, my head is spinning.
Luckily, this is pretty easy to fix. We just need to add a condition which only calls the function under certain conditions (e.g. while the input is greater than zero):
def recurse(i): if i > 0: recurse(i - 1)
Now, if we can this function with some number, we won’t crash:
>>> recurse(5)
But, what is this actually doing? Well, let’s try printing something:
def recurse(i): print(f'Input is {i}') if i > 0: recurse(i - 1)
Here, we used an f-string (learn more about those here) to show the input every time this function is called:
>>> recurse(5) Input is 5 Input is 4 Input is 3 Input is 2 Input is 1 Input is 0
Check that out! We managed to create a function which executes 6 times when we enter a 5. As you can probably imagine, this mechanism can be used to do a lot of interesting things. If you’re interested in learning more about recursion, I’ve written an article all about it.
While Loop
With recursion out of the way, let’s talking about loops. In Python, there are two main looping mechanisms: while
and for
. Typically, courses cover while
first because it’s simpler. If you’re familiar with if statements, a while
loop looks almost exactly the same:
while condition: do_thing()
If the condition is true, the loop body executes just like an if statement. However, after the body executes, the condition is rechecked. If the condition is still true, we drop back into the loop body once again.
Naturally, we can write a loop which behaviors similarly to our recursion example. All we have to do is create a counter variable and count down on each iteration:
i = 5 while i >= 0: print(f'Input is {i}') i -= 1
In this example, we create a variable called i
and give it a value of 5. Then, we kick off the loop by checking if i
is greater than or equal to 0. Since it is, we drop into the loop where we print “Input is 5” and decrement i
. Then, the process repeats. Of course, now i
is 4 instead of 5. Overall time, i
will decrement until it is -1, and the loop condition will fail.
In Python, while
can be used to implement any indefinite loop. In other words, use a while
loop when you don’t know how many iterations you’ll have ahead of time. For example, while
loops are perfect for reading from files or prompting for input from a user. In the next section, we’ll take a look at an example of a definite loop.
For Loop
In many imperative languages like Java, C, and Python, there is more than one way to write a loop. For example, in Java, there are at least four different loop syntaxes that I’m aware of (e.g. while
, for
, for each
, do while
). Since Python tries to keep things simple, the number of loop syntaxes are limited. As far as I know, there are only two: for
and while
.
Now, for
loops in Python aren’t like for
loops in other languages. Instead of providing a space to track an index, they operate more like for each
loops in other languages. In other words, we need something to iterate over like a list. Let’s try recreating our while
loop from above:
indices = [5, 4, 3, 2, 1, 0] for i in indices: print(f'Input is {i}')
To make this loop work, we had to create a list to iterate over. Clearly, this isn’t as convenient as the previous solution. Luckily, Python has a way generating these sort of iterables:
for i in range(5, -1, -1): print(f'Input is {i}')
Here, we’ve created a loop which will count down from 5 to 0 just like all our other loops. To do that, we used the range()
function which generates a list-like structure from the inputs provided. In this case, 5 represents the inclusive starting value, the first -1 represents the exclusive ending value, and the second -1 represents the step (i.e. how many values to skip and in what direction).
In general, for
loops are more useful for iterating over sequences like lists, strings, or generators. In other words, they don’t work exactly like for
loops in other languages—not without using a special function like range()
.
Performance
At this point, I’m not sure it makes sense to compare the performance of these three constructs, but I already wrote three solutions that do the same thing. In other words, we’re just begging for a comparison. To kick things off, let’s store all three of our solutions in strings:
setup = """ i = 5 def recurse(i): # Removed print for sanity if i > 0: recurse(i - 1) """ recursion = """ recurse(5) """ while_loop = """ while i >= 0: # Removed print for sanity i -= 1 """ for_loop = """ for i in range(5, -1, -1): pass # Removed print for sanity """
Then, we can run out test as follows:
>>> import timeit >>> min(timeit.repeat(setup=setup, stmt=recursion)) 0.7848201999999986 >>> min(timeit.repeat(setup=setup, stmt=while_loop)) 0.040824499999999375 >>> min(timeit.repeat(setup=setup, stmt=for_loop)) 0.34835850000000335
One thing I found really interesting was the performance of the while
loop. Then, I realized that my test was slightly inaccurate. Specifically, I had placed the i
in setup, so it became zero after the first iteration. In other words, the while
loop became a glorified if statement. When I updated my setup string, here were the results:
>>> setup = """ def recurse(i): # Removed print for sanity if i > 0: recurse(i - 1) """ >>> while_loop = """ i = 5 while i >= 0: # Removed print for sanity i -= 1 """ >>> min(timeit.repeat(setup=setup, stmt=while_loop)) 0.3415355000000204
Now, that’s almost identical to the for
loop—which makes sense to me. That said, I was reading some performance discussions on StackOverflow, and the for
loop should be faster overall. Naturally, I had to investigate, so I updated both solutions for large numbers:
>>> for_loop = """ for i in range(100, -1, -1): pass # Removed print for sanity """ >>> min(timeit.repeat(setup=setup, stmt=for_loop)) 1.2956954000001133 >>> while_loop = """ i = 100 while i >= 0: # Removed print for sanity i -= 1 """ >>> min(timeit.repeat(setup=setup, stmt=while_loop)) 4.765163399999892
Turns out that 100 was all I was willing to wait. Otherwise, this test may have taken all day. That said, even at a number this small, there’s an obvious difference in performance. Feel free to check out that discussion above for a further explanation of why.
Challenge
Now that we know how to write a loop, let’s try something interesting. Let’s imagine we have a list of lists (aka a matrix):
my_matrix = [ [3, 5, 2, 4], [5, 9, 4, 2], [1, 8, 4, 3] ]
And, we want to total each row (inner list) and determine the average of all rows. Using the example above, we’d get the following row totals:
my_matrix = [ [3, 5, 2, 4], # 14 [5, 9, 4, 2], # 20 [1, 8, 4, 3] # 16 ]
Then, we’d average the totals:
(14 + 20 + 16) / 3 # 16.666666666666668
When we’re done, we’d report the result to the user.
While this seems like a pretty straightforward task for us, how would we train the computer to do it? In other words, how would we use the various loop syntaxes to do this (hint: you’ll might want to nest two loops)?
If you come up with a solution, drop it down below in the comments. Naturally, I’ll throw my own solution down there to get us started.
A Little Recap
With all that out of the way, let’s revisit our solutions once again:
# Recursion def recurse(i): print(f'Input is {i}') if i > 0: recurse(i - 1) recurse(5) # While loop i = 5 while i >= 0: print(f'Input is {i}') i -= 1 # For loop for i in range(5, -1, -1): print(f'Input is {i}')
If you liked this article, you might like joining the weekly mailing list or becoming a Patron. Otherwise, stick around and check out some of these related articles:
- How to Format a String in Python
- How to Write a List Comprehension in Python
- How to Sort a List of Strings in Python
In addition, you might get some value out of the following products on Amazon (ad):
- Python and Algorithmic Thinking for the Complete Beginner
- Python Tricks: A Buffet of Awesome Python Features
If none of that sounds interesting, no sweat! Thanks for checking out my work today.
Recent Posts
Teaching at the collegiate level is a wonderful experience, but it's not always clear what's involved or how you get there. As a result, I figured I'd take a moment today to dump all my knowledge for...
It's been a weird week. I'm at the end of my degree program, but it's hard to celebrate. Let's talk about it.