Python for
Loop
Learning outcomes:
- Syntax of the
for
loop - Using the loop
- The
range()
function - Nesting
for
loops - The
break
andcontinue
keywords - The
return
keyword called from withinfor
Introduction
Looping, or iteration, as it's commonly known, sits at the core of computer automation. You write a piece of code and then tell the computer to execute it repeatedly itself, upto a termination point.
Almost all programming languages provide some kind of constructs that enable iteration in the language, out of the box. Python also has a couple of ways to perform iteration — the for
loop, the while
loop, and iterators and generators.
In this unit, we'll explore the first two of these i.e the for
and the while
loops. Iterators and generators are slightly more advanced topics, which we'll cover later in this course.
This chapter is all about the for
loop, how it operates under the hood, how the range()
function works, and much more.
Let's start learning!
Syntax of for
The general form of the for
loop is shown below:
for item in iterable:
# loop's body
The for
loop takes a given iterable
, starts at its beginning, assigning its first item to the variable item
. Then it executes the block of code following the loop's header, known as the loop's body.
Here, the variable item
is available for use by the developer, which is, in almost all cases, indeed of use.
Once the body of the loop completes, execution moves back to the header. This time the second item is assigned to item
, and then again the loop's body is executed.
The body completes, execution moves back to the header, the third item of iterable
gets assigned to item
, the body gets executed again, and so on.
All this goes on until iterable
gets exhausted i.e there are no more values to iterate over. Once this happens, the for
loop completes and execution moves to the line following the loop.
Now the only thing unclear at this point may be exactly what is an iterable.
What's an iterable?
To simplify things for now, an iterable is a value that is sequence of items. Following from this definition, a string is an iterable as it's a sequence of characters, a list is an iterable since it's a sequence of elements.
As we shall see in the chapter on iterators.
For this matter, range()
also returns a sequence of numbers, specifically an arithmetic sequence of integers. However, this sequence is not defined all once; rather it's defined lazily.
Anyways, now that we know a couple of sequences, let's apply the for
loop on them.
Remember, that in each iteration of the for
loop, the item
variable is the next item in the given sequence.
Using the for
loop
We'll start by looping over strings, followed by lists, and finally over the range()
function.
Recall that strings are a sequence of characters — thereby, iterating over a string using for
would give us each of its characters in each iteration.
An example is shown below.
s = 'Hello'
for char in s:
print(char)
e
l
l
o
We iterate over the string 'Hello'
, and print each of its characters, saved in the variable char
.
Remember that, after the for
keyword, comes the name of the variable to which each item of the given sequence is assigned. This means that it follows the same naming rules as do normal Python variables (well, it is a variable!), and at the same time should be named sensibly.
Here, since each item of the string is a character, we call the variable char
. You could also call it ch
or c
as a further abbreviation for 'char'; or s
following from the first character in the word 'string'.
Now whatever happens inside the loop body makes much more sense as we know that char
is a character.
Based on this approach, let's tackle iteration over lists.
Below we iterate over a list of food items, printing each one as we do so.
food_items = ['Pizza', 'Donut', 'Cake']
for food_item in food_items:
print(food_item)
Donut
Cake
As before, note the name food_item
here. Each element of food_items
is a single food item, and likewise it makes sense to call it food_item
.
Most of the times, this naming convention works i.e the loop variable's name is similar to that of the iterable except for that it's singular whereas the iterable is plural.
A couple more examples are shown below, where this naming convention works well:
for country in countries:
pass
for user in users:
pass
for task in tasks:
pass
Anyways, moving on, similar to lists, it's also possible to iterate over the tuple
data type, as demonstrated below:
nums = (1, 10, 3)
for num in nums:
print(a)
10
3
The range()
function
In many cases, while iterating over a sequence using for
, we need to have the index of the current item.
A simple case is illustrated below:
We have two lists of numbers — a
and b
. Our job is to add consecutive numbers of both these lists and put the sum in a new list c
.
a = [1, 2, 5]
b = [10, 11, 30]
c = []
i = 0
for num in a:
c.append(num + b[i])
i += 1
The code above, surely works, however it doesn't look much representable. One list's items are being retrieved in a variable, without using an index, whereas the other one's items are being retrieved using an index in bracket notation.
Not only this, but it also adds an unnecessary variable i
into the program.
A much better, and common way, to solve such index-requiring problems is to loop over range()
.
The range()
function returns an iterable sequence, defined by arguments passed to it.
range(stop)
If only one arg is supplied, it's assumed as the stop
argument. The sequence of integers begins at 0
and goes uptil stop
, excluding it.
For example, range(5)
denotes the sequence 0, 1, 2, 3, 4; range(2)
denotes the sequence 0, 1; and range(0)
denotes an empty sequence.
If more than one arg is provided, then the first one acts as start
, and the second one as end
, as follows:
range(start, stop[, step])
This time the sequence begins at start
(including it) and goes uptil end
(once again, excluding it).
For example, range(2, 4)
denotes the sequence 2, 3; range(10, 15)
denotes the sequence 10, 11, 12, 13, 14.
The third optional argument step
specifies the difference between consecutive numbers of the sequence. The default step
is 1
.
For example, range(0, 6, 2)
denotes the sequence 0, 2, 4 (remember that 6
is excluded). Similarly, range(10, 20, 3)
denotes the sequence 10, 13, 16, 19.
A negative step can also be given, but in that case stop
must be lesser than start
, otherwise the loop will never end.
For example, range(5, 0, -1)
denotes the sequence 5, 4, 3, 2, 1. Once again, notice that the number stop
is not part of the sequence, since it's excluded.
0
at the end, call range(5, -1, -1)
. This time, the sequence would run from 5 to 0, excluding -1
.Now that we have a bit of experience with range()
, let's use it with the for
loop.
We'll take the same problem above of adding corresponding numbers in two lists to create a third list. This time, instead of iterating over a
directly, we iterate over range(len(a))
. That is, we iterate from the first index to the last index of a
(and b
).
a = [1, 2, 5]
b = [10, 11, 30]
c = []
for i in range(len(a)):
c.append(a[i] + b[i])
Now items of both the lists are retrieved using an index in bracket notation. Everything's consistent and classic!
Nesting for
loops
In programming, it's very common to work with nested loops. There are numerous algorithm out there that utilise single-nested loops; sometimes even double-nested loops.
In this section, we shall cover some of them and see exactly how to work with nested loops.
Let's start by creating an extremely basic single-nested loop:
for i in range(3):
for j in range(3):
print(i, j)
Can you guess the output made by this code?
What's apparent immediately is that the main for
loop runs 3 times. And for each iteration of the main for
loop, the inner for
loop also iterates 3 times. Together this means 9 prints.
In the first iteration i
is 0
. So, what gets printed in the first set of output is 0 0
, 0 1
, and 0 2
.
Keep in mind, that while the inner loop is being executed, i
remains constant. It's only j
that changes with each iteration of the inner loop.
Once the inner loop exits, the main loop's body completes and therefore it moves to its second iteration. i
changes to 1
. Now the next bunch of prints will have i
as 1
— 1 0
, 1 1
and 1 2
.
The second iteration of the main loop completes, paving way for the third one. i
changes to 2
. Now the next bunch of prints are 2 0
, 2 1
and 2 2
.
With this, the third iteration of the main loop completes, moving execution back to its header. Here, it's judged that the given sequence i.e range(3)
, has been exhausted and likewise the loop is exited.
Altogether, we get the following output:
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2
Let's consider a more practical example.
Matrices are used extensively throughout mathematics in a number of places. In Python, we can initialise a matrix using a nested for
loop.
Suppose we want to initialise a 3 x 3 matrix with all 0
's. The following code accomplishes this:
matrix = []
for i in range(3):
# create a new row
matrix.append([])
# fill the row
for j in range(3):
matrix[i].append(0)
In each iteration of the main for
loop, we create a new row in matrix
using matrix.append([])
. Once the row is created, we fill it using the inner for
loop — it adds a 0
in this row, in each of its iterations.
In the end, matrix
is as shown below:
break
and continue
While iterating in a for
loop, there come times when we want to exit the loop before it completes, or simply ignore a given iteration moving to the next one.
These functionalities are provided by the break
and continue
keywords.
As the name suggests:
break
makes execution break out of a given loop.continue
makes execution continue on with the next iteration, skipping the current one.Below we demonstrate where could break
possibly be used.
Suppose we have a list of numbers, where we have to search for a 0
and print "Found"
, as soon as it's found. Accomplishing this using for
would look something as follows:
nums = [10, 5, 30, 0, 8]
for num in nums:
if num == 0:
print('Found')
# end the loop
break
If we omit the break
keyword here, the search would continue on even after we've found a 0
, which is inefficient.
Now let's see how continue
works.
Suppose we have a list of numbers where we want to increment every number if it's not a 0
. Using continue
and for
, this can be done as follows:
nums = [2, 0, 0, 30, 12, 5]
for nums in range(len(nums)):
if nums[i] == 0:
continue
nums[i] += 1
See how the code in line 7 following the if
statement is not encapsulated within an else
block — this is because if the if
's condition gets fulfilled, only then would continue
be executed, as a result directly moving execution to the next iteration and ignoring any following code.
Writing an else
block here isn't invalid — it's just useless!
Note that this code can be — and in fact, should be — written as follows, removing the continue
keyword and instead laying out the conditional in line 4 such that it proceeds only if nums[i]
is not 0
.
nums = [2, 0, 0, 30, 12, 5]
for nums in range(len(nums)):
if nums[i] != 0:
nums[i] += 1
However, for the sake of an example, it's a good way to understand continue
.
Moving on, whenever using the continue
keyword, make sure you don't put it in a place where it might prevent other code from running.
For instance, in the code below we want to display a message 'Zero encountered'
if nums[i]
is equal to 0
, however, we fail to do so:
nums = [2, 0, 0, 30, 12, 5]
for nums in range(len(nums)):
if nums[i] == 0:
continue
print('Zero encountered')
nums[i] += 1
This is because the print()
statement comes after the continue
keyword. As we've stated before, the moment the interpreter comes across continue
in a loop, it ignores everything that follows and moves to the next iteration.
The correct way is to call the print()
statement before continue
:
nums = [2, 0, 0, 30, 12, 5]
for nums in range(len(nums)):
if nums[i] == 0:
print('Zero encountered')
continue
nums[i] += 1
The return
keyword inside for
As we saw back in the Python Functions chapter, a return
statement inside a function resolves a call to that function with a given value. Not only this, but it also takes execution out of the function.
If a loop comes within a function and return
comes within the loop, then the moment the return
keyword is encountered, execution jumps out of the loop, out of the function.
return
is similar to break
in that both take execution out of a loop. The return
keyword additionally resolves the function's call with a given value.
Using return
within loops inside functions is a common practice. Below shown is a very basic example.
def search(val, l):
for item in l:
if item == val:
return True
return False
We create a function that searches for a value within a given list and returns True
if it exists, otherwise False
. Both the list and the value to search for are provided as arguments to the function.
Since we need to conduct a search within the list, we need to go over each of its items. That is, we need a loop in the function.
Inside the loop, we check if the current item of the list is equal to val
. If it is, it simply means that the list contains the value. Consequently, we return True
.
There is no point of going beyond once we've found a match, likewise we use return
directly.
However, if the list doesn't contain the value, the if
statement never executes and likewise when the list gets exhausted by the loop, the statement return False
is executed.
Below, we use this function to search for given values in given lists:
search(10, [0, 2, 3])
search(10, [10, 0, 2])
search('London', ['Paris', 'London', 'Berlin'])
Whenever laying out such loops, make sure that the way you've laid out your conditionals makes sense!
Spread the word
Think that the content was awesome? Share it with your friends!
Join the community
Can't understand something related to the content? Get help from the community.