14  Python conditional loops with while

TipLearning Objectives
  • Learn about while conditionals and how they are used
  • Understand loop control statements (break and continue)
  • Understand that when writing a while loop it is important to consider whether the conditions will enable completion and prevent unintended infinite loops

14.1 while Loops in Python

A while loop in Python is a control flow statement that allows code to be executed repeatedly based on a boolean condition. The loop continues to execute as long as the condition remains True. Here’s a breakdown of how while loops work:

Key Components

  1. Condition: The loop starts with a condition that is evaluated before each iteration. If the condition is True, the code block within the loop is run

  2. Code Block: The statements inside the loop are indented, and these will run repeatedly as long as the condition remains True.

  3. Increment/Decrement: It’s crucial to modify the variable used in the condition within the loop or otherwise ensure that the variable will change such that the condition is not always met; otherwise, you may create an infinite loop. This is often done through incrementing a counter.

  4. Exit: Once the condition evaluates to False, the loop stops, and the program continues with the next line of code following the loop.

Syntax

while condition:
    # Code block to execute
    # Update condition variable

Example 1: Simple Counter

Here’s a simple example to illustrate how a while loop works:

count = 0

while count < 5:
    print("Count is:", count)
    count += 1  # Increment count
Count is: 0
Count is: 1
Count is: 2
Count is: 3
Count is: 4

Explanation of the Example

  • Initialization: The variable count is initialized to 0.

  • Condition: The while loop checks if count is less than 5.

  • Code Execution: If the condition is True, it prints the current value of count.

  • Increment: The line count += 1 increments the value of count by 1 after each iteration.

  • Termination: Once count reaches 5, the condition becomes False, and the loop exits.

Example 2: Tallying Marks

While loops can also be incredibly useful in simulations, or when interacting in the environment. The exact number of iterations may not known be beforehand and depend on a certain condition being met. Here while loops provide a way to repeat actions and process data dynamically within a program.

  • Infinite Loops: Ensure the condition will eventually evaluate to False to avoid infinite loops.

  • Break Statement: You can use the break statement to exit a loop prematurely if needed.

  • Continue Statement: The continue statement can skip the current iteration and proceed to the next one based on a condition.

# List of pre-recorded question marks (including an invalid mark)
marks = [25, 30, -5, 40, 20]

maximum_iterations = 20
total_marks = 0
index = 0

while index < len(marks):

    if index > maximum_iterations:
        break

    current_mark = marks[index]
    index += 1

    # Skip negative values
    if current_mark < 0:
        print(f"Skipping invalid mark: {current_mark}")
        continue

    total_marks += current_mark
    print(f"Added {current_mark} marks. Current total: {total_marks}")

    # Stop execution if total exceeds 100
    if total_marks > 100:
        print("Maximum score of 100 exceeded. Stopping tally.")
        break

print(f"Final score: {total_marks}")
Added 25 marks. Current total: 25
Added 30 marks. Current total: 55
Skipping invalid mark: -5
Added 40 marks. Current total: 95
Added 20 marks. Current total: 115
Maximum score of 100 exceeded. Stopping tally.
Final score: 115

Here the maximum number of iterations is set and if the list is too long the loop will be exited with break. Negative marks are ignored with continue, and if the total exceeds 100 the loop is also exited with break.

Example 3: Guessing game

While loops can also use multiple conditions as well as a decrement, as seen in the below example.

secret_code = "phoenix"
premade_guesses = ["griffin", "shadow", "phoenix", "dragon"]
tries_left = 3
tries_done = 0
index = 0

print("=== Dungeon Vault Unlocking Game ===")
print(f"You have {tries_left} attempts to guess the secret passkey!\n")

while tries_left > 0 and index < len(premade_guesses):
    guess = premade_guesses[index]
    index += 1
    tries_done += 1

    print(f"Attempt {tries_done}: Player guesses '{guess}'")

    if guess == secret_code:
        print(f"Success! The vault opens! You won in {tries_done} attempt(s).")
        break
    else:
        tries_left -= 1
        if tries_left <= 0:
            print("No tries remaining! The vault locks forever. Game over.")
            break
        print(f"Incorrect passkey. Tries left: {tries_left}\n")
=== Dungeon Vault Unlocking Game ===
You have 3 attempts to guess the secret passkey!

Attempt 1: Player guesses 'griffin'
Incorrect passkey. Tries left: 2

Attempt 2: Player guesses 'shadow'
Incorrect passkey. Tries left: 1

Attempt 3: Player guesses 'phoenix'
Success! The vault opens! You won in 3 attempt(s).

14.2 Exercises

ExerciseExercise 1 - Simple Countdown

Level:

Write a simple while loop that counts down from 5 to 1 and then prints "Blast off!".

countdown = 5

while countdown > 0:
    print(countdown)
    countdown -= 1

print("Blast off!")
5
4
3
2
1
Blast off!
ExerciseExercise 2 - Code Legibility

Level:

Consider the following code:

import random

# Setting a fixed seed for reproducible simulation results
random.seed(42)

population = 1000
infected = 1
days = 0
infection_rate = 1.5
max_days = 30

while infected < population:
    days += 1

    if random.random() > 0.9:
        print(f"Day {days}: Lockdown in effect, no new infections today.")
        continue
    
    new_infections = int(infected * infection_rate)

    if new_infections + infected > population:
        new_infections = population - infected
    
    infected += new_infections

    print(f"Day {days}: {infected} infected.")

    if infected >= population:
        print(f"Day {days}: The entire population is infected.")
        break

    if random.random() > 0.8:
        print(f"Day {days}: Health measures implemented, slowing infection.")
        infection_rate -= 0.3
    
    if infection_rate <= 0.1:
        print(f"Day {days}: The infection has nearly stopped spreading.")
        break
    
    if days >= max_days:
        print("The simulation has reached its time limit.")
        break
  1. What does the above code do?

  2. Is it legibly written?

  3. What can be done to improve this?

The simulation models the spread of an infection in a population. Adding comments would help the interpretability of the code in particular for beginners

# Commenting the code appropriately is important!
import random

# Parameters of the simulation
population = 1000       # Total population
infected = 1            # Initially 1 person is infected
days = 0                # Start at day 0
infection_rate = 1.5    # Rate of infection: how many people one infected person can infect per day
max_days = 30           # Maximum number of days to simulate

#While condition checks that not everyone is infected already
while infected < population:
    days += 1

    # Simulate random events like a lockdown with a specific probability
    if random.random() > 0.9:  # 10% chance of stopping the spread for the day
        print(f"Day {days}: Lockdown in effect, no new infections today.")
        continue  # Skip the infection calculation for this day and move to the next

    # Simulate daily infections
    new_infections = int(infected * infection_rate)

    # If new infections exceed the remaining healthy population, adjust them
    if new_infections + infected > population:
        new_infections = population - infected

    infected += new_infections

    # Display day-by-day status
    print(f"Day {days}: {infected} infected.")

    # Continue if there are still people to infect
    if infected >= population:
        print(f"Day {days}: The entire population is infected.")
        break

    # Random chance to reduce the infection rate due to interventions
    if random.random() > 0.8:  # 20% chance to reduce infection rate
        print(f"Day {days}: Health measures implemented, slowing infection.")
        infection_rate -= 0.3  # Decrease the infection rate

    # If the infection rate gets too low, break out of the loop (end of epidemic)
    if infection_rate <= 0.1:
        print(f"Day {days}: The infection has nearly stopped spreading.")
        break

    # Stop the simulation after a max number of days to avoid infinite loops
    if days >= max_days:
        print("The simulation has reached its time limit.")
        break
ExerciseExercise 3 - Interpreting and improving a while loop from the expense calculator

Level:

Consider the following function from input_handler.py which is part of the expense calculator:

def collect_expenses(thresholds, expenses, get_total_func, check_logic_func):
    """Uses a while loop to interact with the user."""
    print("Enter your expenses. Type 'done' to finish.\n")
    
    while True:
        category = input("Category (food, transport, entertainment, other): ").lower()
        if category == "done":
            break
        
        amount_str = input("Amount: ")
        
        # Validation
        if category in thresholds and amount_str.replace('.', '', 1).isdigit():
            amount = float(amount_str)
            expenses.append([category, amount])
            
            # Use the passed-in logic functions
            current_total = get_total_func(expenses, category)
            check_logic_func(category, current_total, thresholds)
        else:
            print("Invalid input. Try again.\n")

Questions:

  1. What does the while statement in collect_expenses do?

  2. What are its limitations?

  3. Improve the code by addressing these limitations.

Note: Notice how functions (get_total_func and check_logic_func) are passed into collect_expenses as arguments. This ensures the code remains modular and flexible.

Use the following functions which are part of the expense calculator for get_total_funcand check_logic_func:

def get_category_total(expenses, category):
    """Simple math logic to filter and sum expenses."""
    return sum(amount for cat, amount in expenses if cat == category)

def check_threshold(category, current_total, thresholds):
    """Handles the conditional logic for budget warnings."""
    limit = thresholds.get(category, 0)
    if current_total > limit:
        print(f"Warning: You have exceeded the {category} threshold!")
        print(f"Total spent in {category}: {current_total}")
    else:
        remaining = limit - current_total
        print(f"Added to {category}.")
        print(f"Total spent in {category}: {current_total}")
        print(f"Amount left for {category}: {remaining}\n")

Explanation:

  1. The while True: loop creates an infinite loop that repeatedly prompts the user for a category and amount to add to the expenses until the user explicitly enters "done".

  2. Limitations:

    • If the user never enters "done", the program will loop indefinitely.
    • There is no upper bound on total entries or expenditure limits.
    • Incorrect input outputs a simple error message but provides no mechanism to cap maximum consecutive invalid entries.

Improved Code Implementation:

def get_category_total(expenses, category):
    """Simple math logic to filter and sum expenses."""
    return sum(amount for cat, amount in expenses if cat == category)

def check_threshold(category, current_total, thresholds):
    """Handles the conditional logic for budget warnings."""
    limit = thresholds.get(category, 0)
    if current_total > limit:
        print(f"Warning: You have exceeded the {category} threshold!")
        print(f"Total spent in {category}: {current_total}")
    else:
        remaining = limit - current_total
        print(f"Added to {category}.")
        print(f"Total spent in {category}: {current_total}")
        print(f"Amount left for {category}: {remaining}\n")

def collect_expenses(thresholds, expenses, get_total_func, check_logic_func, max_entries=10, max_invalid_in_a_row=3):
    """Uses a while loop to interact with the user, with limits added."""
    print("Enter your expenses. Type 'done' to finish.\n")

    entry_count = 0
    invalid_count = 0

    while entry_count < max_entries:
        category = input("Category (food, transport, entertainment, other): ").lower()
        if category == "done":
            break

        amount_str = input("Amount: ")

        # Validation
        if category in thresholds and amount_str.replace('.', '', 1).isdigit():
            amount = float(amount_str)
            expenses.append([category, amount])
            entry_count += 1
            invalid_count = 0  # reset on a valid entry

            # Use the passed-in logic functions
            current_total = get_total_func(expenses, category)
            check_logic_func(category, current_total, thresholds)
        else:
            invalid_count += 1
            print("Invalid input. Try again.\n")
            if invalid_count >= max_invalid_in_a_row:
                print("Too many invalid entries in a row. Stopping.")
                break

    if entry_count >= max_entries:
        print("Maximum number of expense entries reached.")

# Test by running:
thresholds = {"food": 50.0, "transport": 30.0, "entertainment": 20.0}
collect_expenses(thresholds, expenses, get_category_total, check_threshold)
ExerciseExercise 4 - Writing your own while loop

Level:

Either write a while loop of your own choice,

or

Write a script that asks customers repeatedly for drinks they would like to order from a menu.

  • If an item is not recognised from your menu ("tea", "coffee", "water"), display an appropriate message.
  • If the customer enters "stop" or orders more than 10 items, complete the order.
  • Calculate and display the total cost of the order.
# Menu items and prices
menu = {
    "tea": 2.50,
    "coffee": 3.00,
    "water": 1.50
}

def process_order(simulated_inputs):
    total_cost = 0.0
    item_count = 0
    max_items = 10
    input_index = 0

    print("Order processing started...")

    while item_count < max_items and input_index < len(simulated_inputs):
        drink = simulated_inputs[input_index].strip().lower()
        input_index += 1

        if drink == "stop":
            print("Customer closed order.")
            break

        if drink in menu:
            price = menu[drink]
            total_cost += price
            item_count += 1
            print(f"Added {drink.capitalize()} (£{price:.2f}). Total items: {item_count}")
        else:
            print(f"Unknown drink '{drink}'. Please choose tea, coffee, or water.")

    if item_count >= max_items:
        print("Maximum order limit of 10 items reached.")

    print(f"\nOrder Complete! Total drinks ordered: {item_count}. Total cost: £{total_cost:.2f}")

# Example execution with simulated inputs
order_inputs = ["tea", "coffee", "juice", "tea", "water", "stop"]
process_order(order_inputs)
Order processing started...
Added Tea (£2.50). Total items: 1
Added Coffee (£3.00). Total items: 2
Unknown drink 'juice'. Please choose tea, coffee, or water.
Added Tea (£2.50). Total items: 3
Added Water (£1.50). Total items: 4
Customer closed order.

Order Complete! Total drinks ordered: 4. Total cost: £9.50

14.3 Summary

TipKey Points
  • while loops repeat code blocks based on a boolean condition, which is ideal when the required number of iterations is unknown in advance.
  • Always modify condition variables or use explicit safety checks to avoid infinite loops.
  • break exits a loop immediately, whereas continue skips to the next iteration.
  • Passing functions into custom loop routines decouples business logic from control structures, keeping your codebase clean and modular.