Some Notes on Flow Control

A good friend of mine reached out to me for help with a Python assignment he was given in school. I learned that his University also requires a basic programming class, in Python, for all of their students also.

I am a huge supporter of this as I feel writing code will be as important as writing English in the next decade.

His program is a simple CLI application built around a while loop that keeps track of contacts and their birthdays. My friend was kind enough to let me use snippets of his code for this post so thank you Dexter!

The Code In Question

The program spends most of it's execution time in a function called getMenuChoice() which contains a while loop that prints the application's menu.

def getMenuChoice():
    print ("Students and Their Birthdays")
    print('------------------------------------')
    print('1. Add a new birthday')
    print('2. Look up a birthday')
    print('3. Change a birthday')
    print('4. Delete a birthday')
    print('5. Quit Program')
    
    choice = 0
    choice = input('Enter your choice : ')
    while choice != 5:
 
        if choice == 1:
            additem()
            getMenuChoice()
        elif choice == 2:
            getLookup()
            getMenuChoice()
        elif choice == 3:
            changeItem()
            choice = 0
            getMenuChoice()
        elif choice == 4 :
            deleteItem()
            getMenuChoice()
        else:
            print('Please choose an option between 1 and 5')
            getMenuChoice()
    
        print('Thank you! Quitting')
        
def main():
    global birthdays
    global choice
    birthdays = {}
    print(birthdays)
    ADD = 1
    Look_up = 2
    CHANGE = 3
    DELETE = 4
    QUIT = 5
    getMenuChoice()

main()

Whats Wrong?

My friend came to me with two issues:

  • He wasn't sure why, that when entering 5 the program wouldn't exit
  • If you enter 5 after running any prior command, the choice prompt would appear again before the program would exit.

Note: He had to encompass the cli in a while loop

Upon first glance I could point out a small handful of issues, all easy to fix, however I had a feeling the professor really wanted to force his students to use flow control keywords such as continue and break.

I explained to my friend that he had multiple options and sought out to guide him into seeing how these issues could be solved.

A Lesson on Flow Control

Flow Control refers to the statements and keywords that alter a program's flow of execution. Most people are familiar with the ever important if statement. However loops such as for and while also fall into this category as they hold the execution until a specified condition is met.

It is not uncommon for a program to consist of several loops, or even nested loops. Depending on the programmer's requirements, they may want loop to terminate before it's condition is met.

One example is if you are using a loop to iterate over the rows of a table.
Traditionally, there would be another loop inside that first loop to iterate over the columns of each row before the outer loop advances to the next row. The block of code might look something like this:

for rows in table:
    for columns in row:
        if column == 5:
            ...
...

If you are looking for a specific value in the table, such as 5, and that value is found before the last row is reached (before the outer loop completes) we dont want to have to wait for the rest of the rows to be traversed. In the case of a nested loop, a break statement would force both loops to terminate and for execution to resume after the nested loops as if they had completed.

What if we were counting how many of a specific value were in a table, and there was guaranteed to be no duplicates in a row?

That means after we found a value in a row we would want to skip to the next line. In this case, continue could be used to force the current loop to move to it's next iteration. This would effectively terminate the 'columns' for loop and return to the top of the 'rows' for loop where execution would continue inside the loops as if nothing happened.

This can be hard to visualize without a hands on so lets dive into my friends program and take a look at what we changed and why.

The Changes

We are mostly concerned with the while loop inside of getMenuChoice() which is depicted below:

    choice = 0
    choice = input('Enter your choice : ')
    while choice != 5:
 
        if choice == 1:
            additem()
            getMenuChoice()
        elif choice == 2:
            getLookup()
           getMenuChoice()
        elif choice == 3:
            changeItem()
            choice = 0
            getMenuChoice()
        elif choice == 4 :
            deleteItem()
            getMenuChoice()
        else:
            print('Please choose an option between 1 and 5')
            getMenuChoice()
    
        print('Thank you! Quitting')

Issue 1

The first issue we will tackle is why the loop doesn't exit when 5 is pressed.
The while loop's condition is set to choice !=5 which means it should evaluate to True until 5 is pressed. This looks fine on paper, however, when 5 is entered the loop will still complete its final execution bringing it down to line 18 where the else clause is.

The else calls the function recursively where it will start again and set choice = 0 before entering the loop again as if nothing changed. Let's add another elif at line 18 for when choice == 5. This means that anything entered that is not 1-5 will be an error and that the else: clause can properly assume that it will only recieve invalid input. We can then break out of the loop when the 5 is entered.

This leaves the ending looking something like this:

    elif choice == 5:        
        break
    else:
        print('Please choose an option between 1 and 5')
        getMenuChoice()
    
print('Thank you! Quitting')

The break here is forcing the point of execution to exit the while loop immediately bringing execution back to main(). Notice the print() statement was moved backward 4 spaces to bring it outside of the while loop. Without doing this it wouldn't run if a break was called.

Issue 2

We now have the program exiting correctly. Lets tackle the issue where pressing 5 will still prompt the user for a choice if a choice was already made prior.
This is actually a subtlety with how choice is set back to 0 before the loop runs.

choice = 0
    choice = input('Enter your choice : ')
    while choice != 5:

If we move choice = 0 to the start of the while loop it guarantees the variable is 'cleaned' with each execution of the while loop. Finally, instead of using recursion, we can just continue after each menu choice that isn't 5, forcing the loop to start again and choice to be set back to 0.

Before we call this program finished lets force the choice variable to only accept integers using type casting.

 try:
    choice = int(input('Enter your choice : '))
except ValueError:
    print('Please choose a number...')
    continue 

If an invalid input is given to int(), the python interpreter will throw a ValueError so we 'catch' that error, and ask the user to enter a valid input and continue the loop again.

The final result, with all of these fixes applied, looks something like this:

 def getMenuChoice():
    print ("Students and Their Birthdays")
    print('------------------------------------')
    print('1. Add a new birthday')
    print('2. Look up a birthday')
    print('3. Change a birthday')
    print('4. Delete a birthday')
    print('5. Quit Program')

    while True:
        choice = 0
        try:
            choice = int(input('Enter your choice : '))
        except ValueError:
            print('Please choose a number...')
            continue    
        if choice == 1:
            additem()
            continue
        elif choice == 2:
            getLookup()
            continue
        elif choice == 3:
            changeItem()
            continue
        elif choice == 4 :
            deleteItem()
            continue
        elif choice == 5:
            break
        else:
            print('Please choose an option between 1 and 5')
            getMenuChoice()
    
    print('Thank you! Quitting')

Conclusion

If you have trouble fixing a bug in your code and your code is getting more complicated as you try to resolve the issue you may be over-thinking the issue. Try taking a step back and returning to the fundamentals.

While very fundamental, the differences between continue and break are subtle, and easily mistaken. It is good to practice using these keywords to help control any loops you may be using in your programs. They may even drastically speed up and/or simplify your code!

Happy Coding!