Control Flow - Loop Statements

While reading this series, you should type in and run the teacher’s course code in your own Python environment to deepen your understanding.

1. while Loops

(1) The while Loop (Conditional Loop)

Why use loops? First, take a look at the following code

albert_age = 18
guess = int(input(">>:"))
if guess > albert_age :
    print("猜的太大了,往小里试试...")
elif guess < albert_age :
    print("猜的太小了,往大里试试...")
else:
    print("恭喜你,猜对了...")
# 2nd attempt
guess = int(input(">>:"))
if guess > albert_age :
    print("猜的太大了,往小里试试...")
elif guess < albert_age :
    print("猜的太小了,往大里试试...")
else:
    print("恭喜你,猜对了...")
# 3rd attempt
guess = int(input(">>:"))
if guess > albert_age :
    print("猜的太大了,往小里试试...")
elif guess < albert_age :
    print("猜的太小了,往大里试试...")
else:
    print("恭喜你,猜对了...")

No doubt about it, this is some really lame code… because there’s far too much repetition. This happens because in some scenarios we need to check a certain condition repeatedly. For example, when validating a username and password, if the username and password entered by the user don’t match, we usually keep prompting the user to enter them again until they are correct.

Since we can’t know after how many attempts the user will enter the correct input, the code format above can’t be used. In this situation we can perfectly well use a while conditional loop. The while loop is also called a conditional loop, meaning it only executes while the condition is satisfied.

Code example:

albert_age = 18  # albert is only 18
guess = int(input(">>:"))
while guess != albert_age:  # If the guessed age doesn't match the actual age
	if guess > albert_age :
		print("猜的太大了,往小里试试...")
	elif guess < albert_age :
		 print("猜的太小了,往大里试试...")
	guess = int(input(">>:"))
print("恭喜你,猜对了...")

The syntax is as follows:

while condition: # Set a condition
    # Loop body
    # If the condition is true, the loop body executes; after it finishes, the loop repeats and checks the condition again, and keeps running as long as the condition is satisfied.
    # If the condition is false, the loop body doesn't execute and the loop terminates

Note: “the condition is satisfied” means the condition is true. Usually we use True directly to indicate that the condition is true, or use a comparison like count <= 10 in the small exercise below. A false condition can be expressed like this:

False    None    0   ""    ()    []   {}  
# To sum up: False, 0, or anything empty is False

(2) Small while Loop Exercises

# Print 0-10
count=0 # The initial value of count is 0
while count <= 10: # If count is less than or equal to 10
    print('loop',count) # loop1 loop2 ...
    count+=1 # As mentioned earlier, this line is equivalent to count = count + 1
# Print the even numbers between 0 and 10
count=0
while count <= 10:
    if count%2 == 0: # == compares whether the values on both sides are equal; if count%2 equals 0, count is even
        print('loop',count)
    count+=1
# Print the odd numbers between 0 and 10
count=0
while count <= 10:
    if count%2 == 1:
        print('loop',count)
    count+=1

(3) Infinite while Loops

An infinite loop is a loop that runs forever because its condition always holds.

import time
num=0
while True:
    print('count',num)
    time.sleep(1)# Make the program sleep (pause) here for 1 second
    num+=1

(4) Nested while Loops and tag

tag is just a variable, but it’s of boolean type, with only True and False (you could also write 0 or 1). When we have multiple levels of loops, using tag lets us quickly exit all of them

tag=True 
  while tag:
    ......
    while tag:
      ........
      while tag:
        tag=False # Each loop level checks the condition; when tag is false, the loops exit level by level

(5) break and continue in while Loops

This is a key point to understand. It comes up often in later Python exercises such as the shopping cart and the network drive, and it is fairly important syntax in Python and other programming languages.

 # break is used to exit the current loop
while True:
	print("123")
	break
	print("456")

# continue is used to end the current iteration and continue with the next one
while True:
    print('123')
    continue
    print('456')

So the age-guessing program above can be rewritten like this.

albert_age = 18
while True:
    guess = int(input(">>:"))
    if guess > albert_age :
        print("猜的太大了,往小里试试...")
    elif guess < albert_age :
        print("猜的太小了,往大里试试...")
    else:
        print("恭喜你,猜对了...")
        break  # Exit the loop when the user guesses correctly

(6) Combining while and else

Unlike other programming languages, where else is generally only paired with if, Python also has a while … else statement. The else after while means that when the while loop finishes normally without being interrupted by break, the statements after else are executed.

count = 0
while count <= 5 :
    count += 1
    print("Loop",count)
else:
    print("循环正常执行完啦")
print("-----out of while loop ------")

"""
Output
Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
循环正常执行完啦
-----out of while loop ------
"""
# If it's interrupted by break during execution, the else statements won't run
count = 0
while count <= 5 :
    count += 1
    if count == 3:break
    print("Loop",count)
else:
    print("循环正常执行完啦")
print("-----out of while loop ------")

"""
Output
Loop 1
Loop 2
-----out of while loop ------
"""

2. for Loops

(1) The for Loop (Iteration Loop)

The for loop is an iterative loop: for traverses each item in the object being looped over. For example, in the code below, for iterates over every element in range(10), used together with in, i.e. 0, 1, 2, 3…9. The syntax is as follows:

for i in range(10): # range() includes the start but not the end: it starts at 0 and ends at 9, excluding 10
    indented code block

Notes:

  • Here i is each object produced by the iteration; i is just a variable name and can be anything
  • The keywords for and in are required: for…in…
  • range(10) is the object being iterated over. Anything that can hold multiple values can be iterated over; writing a list directly works just the same
  • You can think of iteration like this: the object being iterated over is a mother hen with plenty of eggs in her belly, and the objects produced by the iteration are the eggs

Compute the sum of 0 through 9:

sum_value = 0  # Set the initial value of the sum to 0
for i in range(10):
    sum_value += i 
print(sum_value)

(2) break and continue (same as in while loops above)

(3) Using for Together with else

We often follow a for loop that traverses a sequence or dictionary with an else statement. Many beginners mistakenly think the code after else is executed based on a condition, but that’s not the case: there’s no condition in the for loop; the code after else simply runs after the object has been fully traversed.

# Example 1
L = [1,2,3,4,5,6,7,8,9,10]
for i  in L:
    print(i)
else:
    print('ending')
"""
#Output
>>>
1
2
3
4
5
6
7
8
9
10
ending
"""

# Example 2

dir = {1:'kobe',2:'LBJ',3:'CP3',4:'TDK'}# Dictionary format: 1 2 3 4 are the keys, Kobe LBJ... are the values corresponding to the keys
for k,v in dir.items():
    print (k,v)
else:
    print('ending')

"""
#Output
1 kobe
2 LBJ
3 CP3
4 TDK
ending
"""

3. Notes on Using Control Flow Statements

(1) Avoid Deeply Nested Branches

If this article could be cut down to a single sentence, that sentence would definitely be “do everything you can to avoid nested branches.” Overly deep branch nesting is one of the most common mistakes made by programming beginners. If a novice programmer writes many levels of nested branches, you may see layer upon layer of braces: if: if: if: ... else: else: else: ..., commonly known as “Nested If Statement Hell”. If it can be avoided, replace it with another approach whenever possible. Such deep nesting is very bad for code readability, especially when there’s a lot of code under an if branch.

(2) Encapsulate Overly Complex Logical Conditions

If the expression in a conditional branch is too complex and contains too many not/and/or, the readability of the code drops sharply. In that case we can break it apart, or first negate it with not. Negation means that if the original value is True, it becomes False after not, and if the original value is False, it becomes True after not.

(3) Watch Out for Duplicate Code Across Branches

Duplicate code is the natural enemy of code quality, and conditional branch statements very easily become a hotbed of duplicate code. So when writing conditional branches, pay special attention not to produce unnecessary duplicate code. When you write branch code, pay extra attention to duplicate code blocks created by the branches; if you can easily eliminate them, don’t hesitate.

(4) Use “De Morgan’s Laws”

When making branch decisions, we sometimes write code like this:

# If the user's account has no balance or the user has no overdraft limit, refuse the purchase
# The following is pseudocode
if not “has balance” or not “has overdraft limit”:
    print("拒绝用户购买")

When you first see this code, don’t you need to think for a while to understand what it’s trying to do? That’s because the logical expression above contains 2 nots and 1 or, and we humans happen to be bad at handling too many “negations” and “or” relationships. This is where De Morgan’s laws come in. Put simply, De Morgan’s law says that not A or not B is equivalent to not (A and B). With this transformation, the code above can be rewritten as:

if not (“has balance” and “has overdraft limit”):
    print("拒绝用户购买")

(5) Use all() / any() in Conditions

The two functions all() and any() are very well suited for use in conditions. Both accept an iterable and return a boolean, where:

  • all(seq): returns True only if all objects in seq are truthy, otherwise returns False
  • any(seq): returns True as long as any object in seq is truthy, otherwise returns False

Suppose we have the following code:

def all_numbers_gt_10(numbers):
    # Return True only if all numbers in the sequence are greater than 10
    if not numbers:  # If numbers is empty; here numbers represents a list, in a format like [1, 2, 3...]
        # For lists, the empty list [] is False; this line checks whether numbers is empty, and returns False if it is
        return False
    for n in numbers:  # Iterate over every element in numbers
        if n <= 10:
            return False  # If any element is less than or equal to 10, the function returns False immediately
    return True  # If all elements in the numbers list are greater than 10, return True

Using the built-in function all() together with a simple generator expression, the code above can be written like this:

def all_numbers_gt_10_2(numbers):
   return bool(numbers) and all(n > 10 for n in numbers)

Simple and efficient, with no loss of usability.

(6) Use the else Branch of try/while/for

Let’s look at this function:

def do_stuff():
   first_thing_successed = False
   try:
       do_the_first_thing()# Do the first thing
       first_thing_successed = True# The first thing succeeded, so set the flag to True
   except Exception as e: # If the two lines above (the two lines in try) raise an error, the first thing did not succeed; execute the statements below
       print("Error while calling do_some_thing")
       return
   # Do the second thing only if first_thing completed successfully
   if first_thing_successed:
       return do_the_second_thing()

In the function do_stuff, we want to proceed with the second function call only after do_the_first_thing() has been called successfully (that is, without raising any exception). To achieve this, we need to define an extra variable first_thing_successed as a flag. In fact, we can achieve the same effect in a simpler way:

def do_stuff():
   try:
       do_the_first_thing()
   except Exception as e:
       print("Error while calling do_some_thing")
       return
   else:
       return do_the_second_thing()

After appending an else branch to the end of the try block, do_the_second_thing() under that branch will only run after all the statements under try have completed normally (that is, with no exception, and no return, break, etc.). Similarly, Python’s for/while loops also support adding an else branch, meaning: the code under the else branch is executed only after the iterable used by the loop has been exhausted normally, or after the condition used by the while loop becomes False.

(7) Mind the Precedence of and and or

Look at the two expressions below. Can you guess whether their values are the same?

(True or False) and False

True or False and False

The answer: no, they’re not. Their values are False and True respectively. Did you guess right? The key is that the and operator has higher precedence than or. So the second expression above is actually seen by Python as True or (False and False), and the result is therefore True, not False. When writing expressions containing multiple ands and ors, pay extra attention to the precedence of and and or. Even if the order of evaluation happens to be exactly what you need, you can add extra parentheses to make the code clearer.