Practice

Control statements and looping

if, else, and elif

Let’s practice our branching statements. Remember that elif (short for else if) is an optional branch that will let you add another if test, and else is an optional branch that will catch anything not previously caught by if or elif.

>>> def test_number(number):
...     if number < 100:
...         print("This is a pretty small number")
...     elif number == 100:
...         print("This number is alright")
...     else:
...         print("This number is huge!")
...
>>> test_number(5)
>>> test_number(99)
>>> test_number(100)
>>> test_number(8675309)
Here's what you should have seen in your REPL:

You can also have multiple conditions in an if statement. This function prints “Fizzbuzz!” if the number is divisible by both 3 and 5 (the % or modulo operator returns the remainder from the division of two numbers):

>>> def fizzbuzz(number):
...     if number % 3 == 0 and number % 5 == 0:
...         print("Fizzbuzz!")
...
>>> fizzbuzz(3)
>>> fizzbuzz(5)
>>> fizzbuzz(15)
Here's what you should have seen in your REPL:

Let’s also practice using if to test for an empty list. Remember that an empty list is “Falsey”, or resolves to False. Write a function to print a list of elements, or an error message if the list is empty. Print a special message if a list item is None:

>>> def my_func(my_list):
...     if my_list:
...         for item in my_list:
...             if item is None:
...                 print("Got None!")
...             else:
...                 print(item)
...     else:
...         print("Got an empty list!")
...
>>> my_func([1, 2, 3])
1
2
3
>>> my_func([2, None, "hello", 42])
2
Got None!
hello
42
>>> my_func([])
Got an empty list!

The for loop, range() and enumerate()

Let’s try making a list and looping over it:

>>> my_list = [0, 1, 2]
>>> for num in my_list:
...     print(f"Next value: {num}")
...

If we’re just interested in looping over a list of numbers, we can use the range() function instead. Remember that the first argument is inclusive and the second is exclusive:

>>> for num in range(0, 3):
...     print(f"Next value: {num}")
...

Another useful function is enumerate(), which iterates over an iterable (like a list) and also gives you an automatic counter. enumerate() returns a tuple in the form of (counter, item).

>>> my_list = ["foo", "bar", "baz"]
>>> for index, item in enumerate(my_list):
...     print(f"Item {index}: {item}")
...

We can also loop over a dictionary’s keys and/or values. If you try to iterate over the dictionary object itself, what do you get?

>>> my_dict = {"foo": "bar", "hello": "world"}
>>> for key in my_dict:
...     print(f"Key: {key}")
...
# This is equivalent to...
>>> for key in my_dict.keys():
...     print(f"Key: {key}")
...

The keys() method returns the dictionary’s keys as a list, which you can then iterate over as you would any other list. This also works for values()

>>> for value in my_dict.values():
...     print(f"Value: {value}")
...

The most useful function, however, is items(), which returns the dictionary’s items as tuples in the form of (key, value):

>>> for key, value in my_dict.items():
...     print(f"Item {key} = {value}")
...
Here's what you should have seen in your REPL:

break, continue, and return

break and continue are important functions for controlling the program flow inside loops. break ends the loop immediately and continues executing from outside the loop’s scope, and continue skips the remainder of the loop and continues executing from the next round of the loop. Let’s practice:

>>> for num in range(0, 100):
...     print(f"Testing number {num}")
...     if num == 3:
...         print("Found number 3!")
...         break
...     print("Not yet...")
...

Notice that “Not yet…” doesn’t get printed for number 3, because we break out of the loop first. Let’s try a continue:

>>> for num in range(0, 100):
...     print(f"Testing number {num}")
...     if num < 3:
...         continue
...     elif num == 5:
...         print("Found number 5!")
...         break
...     print("Not yet...")
...

Notice that “Not yet…” doesn’t get printed at all until the number is 3, because the continue short-circuits the loop back to the beginning. Then we break when we hit 5.

You can also use the return keyword to break out of a loop within a function, while optionally returning a value.

>>> def is_number_in_list(number_to_check, list_to_search):
...     for num in list_to_search:
...         print(f"Checking {num}...")
...         if num == number_to_check:
...             return True
...     return False
>>> my_list = [1, 2, 3, 4, 5]
>>> is_number_in_list(27, my_list)
>>> is_number_in_list(2, my_list)

Notice that our function is_number_in_list checks all the numbers in my_list on the first run, but on the next run, stops immediately when it hits 3 and returns True.

Here's what you should have seen in your REPL:

while loop

Instead of looping over a sequence, while loops continue looping while a certain condition is met (or not met). The condition is checked at the beginning every iteration.

>>> counter = 0
>>> while counter < 3:
...     print(f"Counter = {counter}")
...     counter += 1

Notice that the loop ends once counter 3, and the remainder of the loop is bypassed. You can also loop forever by using while True or while False, but you should make sure you have solid break conditions, or your program will just loop forever (unless that’s what you want).

>>> counter = 0
>>> while True:
...     print(f"Counter = {counter}")
...     if counter == 3:
...         break
...     counter += 1
Here's what you should have seen in your REPL:

Nested Loops

Nesting loops is often necessary and sometimes tricky. The break keyword will only get you out of whichever loop you’re breaking. The only way to exit all loops is with multiple break statements (at each level), or the return keyword (inside a function). For example:

names = ["Rose", "Max", "Nina"]
target_letter = 'x'
found = False

for name in names:
    for char in name:
            if char == target_letter:
                    found = True
                    break

    if found:
        print(f"Found {name} with letter: {target_letter}")
        break

Or:

>>> for x in range(0, 5):
...     for y in range(0, 5):
...         print(f"x = {x}, y = {y}")
...         if y == 2:
...             break
...

Notice how the inner y loop never gets above 2, whereas the outer x loop continues until the end of its range.

Here's what you should have seen in your REPL: