To Infinity and Beyond • The Infinite `for` Loop
Yes, the `while` loop is what you'd normally use for infinite loops. But you can also use `for` loops, with help from infinite iterators
I'm guilty of this, too. When I teach beginners, I often tell them that the for
loop is useful when you have a fixed number of iterations and that the while
loop is ideal when the program needs to decide when to stop iterating based on what happens in the program.
The obvious conclusion is that if you want an infinite loop, you'll need to use the while
loop—while True
usually does the trick.
But you can also use a for
loop to create an infinite loop. Let's explore further.
I'll keep today's article short, partly because I'm in the final stages of completing The Python Coding Book, which is taking up most of my time this week. The Python Coding Book has been available in "beta version" online for almost three years. During this time, it has gained lots of dedicated followers—and it now shows up in one of the top places on Google for the generic search term "python book".
My plan has always been to publish The Python Coding Book as a book, not just a website. That's why there's "book" in the title! And I'm annoyed at myself for taking so long. But now is the time. I'm writing this article as I take a break from making the final edits to the book and finalising the cover.
Of course, I'll let you know once the book is published and available for anyone who wants either the hard copy (in paperback—it will look good on your shelf) or a PDF/e-book.
Infinite Animation
The focus of this short article (it's definitely not an infinite article) is on creating infinite for
loops with infinite iterators. Almost any program with an infinite loop could be used. But I'll use this fun infinite animation in this article:
You may notice the word infinite appears often in this article. But it appears a finite number of times!
Infinite Iterators
Let's focus on the infinite iterator to use in an infinite for
loop bit first. I'll leave the steps needed to make the animation look pretty until the end.
The way up
Let's start creating a screen and a dot and move the dot through the five positions it can occupy:
The list positions
includes the five y-coordinates for the dot's position. You create a screen and Turtle
object and change the shape and size to make the sprite look like a dot. The .penup()
method prevents the Turtle
object from drawing a line every time it moves.
You can run this code, but you'll see the dot race through the five positions rapidly. Let's add a pause after the dot reaches each of its positions. I'm truncating the code to only show sections with changes:
Here's the dot making its way across the five positions:
And the way down
You can make the dot go back down after it reaches the top position by extending the list. But let's make this code more robust for future changes. First, let's replace the list with a range
object to make it easier to update the y-coordinates:
The variable positions
is no longer a list. It's now a range
object that represents the integers from -200
up to but excluding 201
in steps of 100
. A reminder that the end of the range is excluded. This is why the end value is 201
rather than 200
.
This works since the range
object is iterable, and therefore, it can be used in a for
loop. You can read more about iterables in Iterable: Python's Stepping Stones.
Let's find an efficient way of going through a sequence from beginning to end and then back to the beginning. It's best to explore this in a REPL/Python Console session first:
You can extend the list by adding a reversed version of itself. The slice ::-1
reverses the list. This is fine, but the endpoints are repeated. The numbers 1
and 5
appear twice in the extended list. Let's fix this:
The slice in the second list is now -2:0:-1
. Let's look at all parts of this slice:
The slice starts at the element with index
-2
. This is the second element from the end, which is4
. This element is included in the slice, since the start of the range in a slice is included.The slice ends just before the element with index
0
. This is the first element ofnumbers
, which is1
. The end value of a range in a slice is excluded from the slice. Therefore,1
won't be included. Note that this is not the same as the slice-2::-1
, since you need to exclude the first element of the list.The slice
-2:0
would return an empty slice, as the default step size is1
. You cannot start from the second-from-last element and get to the first element in steps of1
. You need steps of-1
. This is the final integer used in the slice notation.
Brilliant. Let's apply this to the animation code:
Oops!
Traceback (most recent call last):
...
all_positions = positions + positions[-2:0:-1]
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for +: 'range' and 'range'
You can't extend a range
object using the addition operator +
. The range
object doesn't have the __add__()
special method. You can confirm this by running range.__add__
in the REPL. It raises an AttributeError
.
Let's get back to the safety of lists, then:
You cast the original range
object into a list, so you can still get the benefit of defining the y-positions using the range()
notation, and you also get the benefit of using a list since you can extend the sequence to make it go back to the beginning.
Note that the for
loop now iterates through the new list all_positions
. Here's the animation so far:
And up and down and up and...
The dot climbs up the ladder with 5 rungs and then climbs down again. But now, you need the dot to keep climbing up and down in eternum.
Enter stage right: the itertools
module and the cycle()
function†.
You can create an iterator from an existing iterable, which keeps cycling from the beginning to the end forever—infinitely! If you need to refresh your memory about what iterators are and the difference between iterators and iterables—they're not the same—you can read A One-Way Stream of Data • Iterators in Python.
You can import itertools
and update all_positions
to assign the infinite cycle
iterator to it instead of a finite list:
Run this code and you'll get an infinite animation. The for
loop iterates through all_positions
, but all_positions
is an infinite iterator. Therefore, it will always fetch the next item when the for
loop asks for it. Forever, to infinity and beyond.
† Don't start looking for postage stamps for your complaint letters! Yes, cycle()
is not a function. The name cycle
refers to the class. Therefore, cycle()
is the constructor that creates an instance of the class. But we use it like a function, and Python is cool about this. It fits nicely with Python's duck-typing philosophy where an object's behaviour is what matters rather than what it is!
Infinite Customisation
It's time to make the animation look prettier. Let's change colours and put labels to show the y-positions.
I'll add this publication's brand colours as RGB values. By default, the turtle
module uses float RGB values between 0 and 1. But you can change its colour mode to use integers between 0 and 255 instead:
The colours are defined as RGB tuples, and window.colormode(255)
ensures the turtle
module is set to use RGB values in the 0..255 range. You use these colours to change the screen's background colour and the dot's colour.
Here's the animation after these changes:
Finally, you can add the labels to show the y-positions. You can use the original positions
for this since you only need to write each label once!
This final step includes two additions. First, you create a new Turtle
object named label
and set its colours and other initial conditions. This object's role is to write text on the screen. Next, you add a for
loop to iterate through positions
and write the y-positions on the screen.
You could add window.tracer(0)
soon after creating window
and window.update()
in the animation for
loop if you want more control on when things are displayed. But this is not too relevant in this animation, so I won't add it to my final version!
As promised, I did not go on forever in this article…
Code in this article uses Python 3.12
Two announcements:
• Coming soon: The Python Coding Book, the proper First Edition in paperback and e-book. The online version is the Zeroth Edition!
• The first hybrid cohort course starts in April: Beyond the Basics. This is included with membership of The Python Coding Place.
If you read my articles often, and perhaps my posts on social media, too, you've heard me talk about The Python Coding Place several times. But you haven't heard me talk a lot about is Codetoday Unlimited, a platform for teenagers to learn to code in Python. The beginner levels are free so everyone can start their Python journey. If you have teenage daughters or sons, or a bit younger, too, or nephews and nieces, or neighbours' children, or any teenager you know, really, send them to Codetoday Unlimited so they can start learning Python or take their Python to the next level if they've already covered some of the basics.
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles. Alternatively, if you become a member of The Python Coding Place, you'll get access to all articles on The Stack as part of that membership. Of course, there's plenty more at The Place, too.
Appendix: Code Blocks
Code Block #1
import turtle
positions = [-200, -100, 0, 100, 200]
window = turtle.Screen()
sprite = turtle.Turtle()
sprite.penup()
sprite.shape("circle")
sprite.turtlesize(2)
for position in positions:
sprite.sety(position)
turtle.done()
Code Block #2
import time
import turtle
# ...
for position in positions:
sprite.sety(position)
time.sleep(0.3)
turtle.done()
Code Block #3
# ...
positions = range(-200, 201, 100)
# ...
for position in positions:
sprite.sety(position)
time.sleep(0.3)
turtle.done()
Code Block #4
numbers = [1, 2, 3, 4, 5]
numbers + numbers[::-1]
# [1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
Code Block #5
numbers + numbers[-2:0:-1]
# [1, 2, 3, 4, 5, 4, 3, 2]
Code Block #6
import time
import turtle
positions = range(-200, 201, 100)
all_positions = positions + positions[-2:0:-1]
# ...
Code Block #7
import time
import turtle
positions = list(range(-200, 201, 100))
all_positions = positions + positions[-2:0:-1]
# ...
for position in all_positions:
sprite.sety(position)
time.sleep(0.3)
turtle.done()
Code Block #8
import itertools
import time
import turtle
positions = list(range(-200, 201, 100))
all_positions = itertools.cycle(
positions + positions[-2:0:-1]
)
window = turtle.Screen()
sprite = turtle.Turtle()
sprite.penup()
sprite.shape("circle")
sprite.turtlesize(2)
for position in all_positions:
sprite.sety(position)
time.sleep(0.3)
turtle.done()
Code Block #9
import itertools
import time
import turtle
orange = 253, 179, 59
green = 26, 107, 114
positions = list(range(-200, 201, 100))
all_positions = itertools.cycle(
positions + positions[-2:0:-1]
)
window = turtle.Screen()
window.colormode(255)
window.bgcolor(green)
sprite = turtle.Turtle()
sprite.color(orange)
sprite.penup()
sprite.shape("circle")
sprite.turtlesize(2)
for position in all_positions:
sprite.sety(position)
time.sleep(0.3)
turtle.done()
Code Block #10
import itertools
import time
import turtle
orange = 253, 179, 59
green = 26, 107, 114
positions = list(range(-200, 201, 100))
all_positions = itertools.cycle(
positions + positions[-2:0:-1]
)
window = turtle.Screen()
window.colormode(255)
window.bgcolor(green)
label = turtle.Turtle()
label.color(orange)
label.penup()
label.hideturtle()
for position in positions:
label.setposition(150, position)
label.write(
position,
font=("Courier", 30, "bold"),
)
sprite = turtle.Turtle()
sprite.color(orange)
sprite.penup()
sprite.shape("circle")
sprite.turtlesize(2)
for position in all_positions:
sprite.sety(position)
time.sleep(0.3)
turtle.done()