Lab 5: Other Early Exits from Loops

In the previous part, we saw how a return from a function can terminate a loop early (by ending the execution of the function body, which effectively stops any code executing in the function).

Here we explore Python constructs for changing control flow in a loop that do not require the return construct or embedding the loop in a function.

Table of Contents

The break statement

In both for and while loops, break means "exit the innermost loop in which the break appears, and transfer control to the statement following the loop." Consider this testBreak function:

def testBreak(nums):
    for n in nums:
        if n > 10:
            break
        print(n)
    print('after for')

The call testBreak([5, -2, 17, -4, 6]) prints

5
-2
after for

because when n is 17, the break causes the for loop to end, and the next statement after the for loop is executed.

In contrast in the call testBreak([5, -2, -4, 6]), the break statement is never executed in the loop, so all number in the list are printed, and and for loop finishes normally when the last number in the list is processed.

5
-2
-4
6
after for

The for/else statement

A problem with break is that it's not easy to tell whether the statement after the loop is reached because the loop finished normally or whether it was reached because of a break.

To distinguish these cases, a boolean flag variable can be introduced to distinguish the two cases, such as didBreak in the following example:

def testBreakFlag(nums):
    def testBreakFlag(nums):
    didBreak = False
    for n in nums:
        if n > 10:
            didBreak = True
            break
        print(n)
    if didBreak:
        ... code to execute in break case ...
    else:
        ... code to execute in normal loop completion case ...

But Python provides a handy special for/else statement that reduce the need for such a flag:

def testBreakElse(nums):
    for n in nums:
        if n > 10:
            break
        print(n)
    else:
        print('for else (no break)')
    print('after for')

In the above example, the print('for else (no break)') is executed only in the case where break is not executed during the regular loop body. So testBreakElse([5, -2, 8, -4, 6]), for which no break is executed, prints

5
-2
8
-4
6
for else (no break)
after for

but testBreakElse([5, -2, 17, -4, 6]), which executes the break when n is 17, prints only three lines:

5
-2
after for

The syntax of for/else is unnecessarily confusing. In the above example, the else: does not belong with the if in the body of the for loop; instead it is part of the for loop itself and means "execute the else: code only when the loop completed normally (i.e., no break was executed). It would be better better if else: were replaced by something like noBreak:

BTW, there is also a corresponding while/else statement for while loops that behaves analogously .

The continue statement

def testContinue(nums):
    for n in nums:
        if n < 0:
            continue
        print(n)
    print('after for')

Both for and while loops support a continue statement, which means "stop executing the statements in the current iteration of the loop, but start the next iteration of the loop. For example, for the function

def testContinue(nums):
    for n in nums:
        if n < 0:
            continue
        print(n)
    print('after for')

the call testContinue([5, 2, -3, 17, -1, -4, 6]) prints only the nonnegative numbers before moving on to the statement after the loop:

5
2
17
6
after for

You might well wonder why

        if n < 0:
            continue
        print(n)

isn't just replaced by

        if n >= 0:
           print(n)

In this particular case, that would be an easy transformation to make. But in general, the point at which the realization is made that it's desirable to punt the current iteration and go to the next one might be deeply nested in the code and require a complex rewrite of the existing code in order to implement this behavior. Having a simple continue statement gets the same effect without having to rewrite the code!

All Together Now

def testExits(string):
    for char in string:
        if char.isdigit():
            continue
        elif char == '!':
            break
        print(f"in for body: {char}")
    else:
        print(f"in for else (no break)")
    print(f"after for")

Consider the above testExits function. Assess your understanding of break, continue, and for/else by predicting the printed output of the following two calls. predictions by executing the code in Thonny).

A Complex Example

This section ends with the following complex function that uses nested for/else loops with break statements. See if you can correctly predict the printed output of the 4 function calls after the definition.

def findMultiple(nums, n1, n2):
    for n in nums:
        if n%n1 == 0:
            print(f"First multiple of {n1} is {n}")
            break
    else:
        for n in nums:
            if n%n2 == 0:
                print(f"First multiple of {n2} is {n}")
                break
        else:
            print(f"Found no multiples of {n1} or {n2}")
    print('Done with problem.')
  1. findMultiple([17, 28, 6, 20, 14], 7, 4)

    Show Answer
    First multiple of 7 is 28
    Done with problem.
    
  2. findMultiple([17, 29, 6, 20, 14], 7, 4)

    Show Answer
    First multiple of 7 is 14
    Done with problem.
    
  3. findMultiple([17, 29, 6, 20, 13], 7, 4)

    Show Answer
    First multiple of 4 is 20
    Done with problem.
    
  4. findMultiple([17, 29, 6, 19, 13], 7, 4)

    Show Answer
    Found no multiples of 7 or 4
    Done with problem.
    

print('-'40) findMultiple([17, 28, 6, 20, 14], 7, 4) print('-'40) findMultiple([17, 29, 6, 20, 14], 7, 4) print('-'40) findMultiple([17, 29, 6, 20, 13], 7, 4) print('-'40) findMultiple([17, 29, 6, 19, 13], 7, 4)



Done with problem.

Done with problem.

Table of Contents