Python for Loop

Chapter 19 18 mins

Learning outcomes:

  1. Syntax of the for loop
  2. Using the loop
  3. The range() function
  4. Nesting for loops
  5. The break and continue keywords
  6. The return keyword called from within for

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)
H
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)
Pizza
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
Variable naming sure has conventions but, at the end of the day, it's a skill one learns with time and experience.

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)
1
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.

In order to denote the same sequence above but with a 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 11 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 0
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, matrixis as shown below:

matrix
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]

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])
False
search(10, [10, 0, 2])
True
search('London', ['Paris', 'London', 'Berlin'])
True

Whenever laying out such loops, make sure that the way you've laid out your conditionals makes sense!