Python control flow#
Programs are shaped by control flow, which determines which commands run at which times. Python programs frequently rely on a few key mechanisms of control flow.
If statements#
Sometimes, we need to decide whether to run a command or not, depending on the conditions. To do this, we can write an if statement:
if some_condition:
code_to_run_if_true
The simplest form of an if
statement is to run some code, only if a condition is satisfied. For example, say if we want to check if one variable is larger than another and run some code only if it is.
a = 5
b = 8
if a > b:
print("a is greater than b.")
Because \(a < b\), the code is not executed.
We could instead check if the opposite is true.
if b > a:
print("b is greater than a.")
b is greater than a.
Here, the code checks if \(b>a\), finds that it is, and runs the code to print our message.
We can optionally also include an else
block at the end. The code here will run if the condition is not true.
if b > a:
print("b is greater than a.")
else:
print("b is not greater than a.")
b is greater than a.
Exercise: simple if statements#
Write a function called issmall
that takes in a positive number and uses an if
statement to test if it is less than 10. It should return True
if the integer is less than 10 and return False
otherwise. Test it with inputs 1 and 10.
Advanced#
Write a function that works the same way, without using an if
statement.
# answer here
We can put multiple checks together using an if/elif
statement. The elif
keyword is short for “else if”. Different conditions are checked in order. If the first condition is true, then the code in that block will be executed. If the first condition is false, then the elif
condition will be checked. You can have as many elif
conditions as you want.
a = 5
b = 8
if a > b:
print("a is greater than b.")
elif b > a:
print("b is greater than a.")
b is greater than a.
Finally, we can optionally also have an else
block at the end. This will run code only if none of the other conditions were met.
a = 5
b = 5
if a > b:
print("a is greater than b.")
elif b > a:
print("b is greater than a.")
else:
print("a is equal to b.")
a is equal to b.
To summarize, the syntax for if/elif/else
statements is:
if condition:
code_to_run
elif other_condition:
different_code
else:
code_if_neither_condition_true
The various parts of an if/elif/else
statement can take any expression that returns a boolean value (that is, True
or False
). You can get a boolean using tests of equality (using ==
to test if two variables have the same value, or !=
to test if they have different values) or inequalities (<
, >
, <=
, >=
).
number = 4
string = "name"
print(number < 4)
print(number <= 4)
print(string == "name")
print(string != "name")
False
True
True
False
Exercise: if statements#
Define a function called greet
prints out a greeting. It should take one input called user
. If the user is "Mark"
, print "Hi, Mark S.!
. If the user is "Helena"
, print "Hello, Helly R."
Advanced#
Add an else
block so that, if the user is anyone else, the function will use an f-string to print "Greetings, [user]."
# answer here
Sometimes we may want to combine tests to check for a more complicated set of conditions. We can combine tests using the keywords and
and or
.
The and
keyword checks if two statements are both True.
print(1 == 1 and 2 > 1) # both parts are True, so this returns True
print(1 == 1 and 2 < 1) # the first part is True, but the second is False
True
False
The or
keyword checks if either statement is True, or if both are True.
print(1 == 0 or 2 > 1) # the second part is True, so this returns True
print(1 == 0 or 2 < 1) # both parts are False, so this returns False
True
False
Finally, we can also use the keyword not
to get the opposite of some conditional. Sometimes it is easier to check if something does not meet some condition than to check if it does.
For example, sometimes a variable will be initialized to None
to indicate that it is undefined. We can check this using not
.
def process_condition_code(condition):
if condition is not None:
print(f"Current condition is {condition}.")
else:
print("Current condition is undefined.")
process_condition_code(1)
process_condition_code(None)
Current condition is 1.
Current condition is undefined.
Let’s look at a common example of combining conditionals. For example, say we want to test if a number is between 5 and 10 (inclusive). We’ll write a function to run this check and try it with some different numbers.
def check_in_range(x, lower=5, upper=10):
return x >= lower and x <= upper
print(check_in_range(4))
print(check_in_range(6))
print(check_in_range(11))
False
True
False
Note that we used keyword arguments for our function, so that we can change the range from the default if we want.
print(check_in_range(0.5, lower=0, upper=1))
print(check_in_range(1.5, lower=0, upper=1))
True
False
Note that we could have also written the same thing using if
statements.
def check_in_range2(x, lower=5, upper=10):
if x >= lower:
if x <= upper:
return True
else:
return False
else:
return False
print(check_in_range(4))
print(check_in_range(6))
print(check_in_range(11))
False
True
False
This also works, but is harder to read. In this case, it’s better to combine your tests into one statement, x >= lower and x <= upper
.
Exercise: combining conditionals#
Write a function called out_of_range
that takes in a number x
and checks if it is not between 0 and 1. If \(0 \ge x \ge 1\), it should return False
; otherwise, it should return True
.
Advanced#
Add keyword arguments for lower
and upper
bounds, with 0 and 1 as the defaults. Change your function to use those bounds instead. Having variables like 0 and 1 specified directly in the code is known as hard coding. By having these be arguments to the function instead, your code becomes more flexible and less “hard coded”.
# answer here
For loops#
A common pattern in programming is running some commands multiple times using a loop.
In Python, we can do this using a for
loop:
for item in items:
some_code_to_run_for_each_item
A common pattern is to loop over some items in a list and do something with each item in the list.
words = ["piano", "fountain", "hollow", "pupil"]
for word in words:
print(word)
piano
fountain
hollow
pupil
Here, word
is a new kind of variable we haven’t seen yet. It changes its value at each step of the loop. First, it’s "piano"
, then "fountain"
, etc., until the end of the list of words. Each time we run print(word)
, we’re printing a different word in the list.
Let’s take another look at what happens during a for
loop. If we run the following loop:
numbers = [1, 2, 3]
for number in numbers:
print(number)
Each step of the loop accesses a different part of the list:
numbers = [1, 2, 3]
number = ^
number = 1
numbers = [1, 2, 3]
number = ^
number = 2
numbers = [1, 2, 3]
number = ^
number = 3
We can use for
loops for lots of things. One example is to loop over every item in a list and create a new list by modifying that list.
cap = []
for word in words:
cap.append(word.capitalize())
print(cap)
['Piano', 'Fountain', 'Hollow', 'Pupil']
Here, we first initialized a new list by setting cap = []
. This creates an empty list with no items in it. Next, we looped over the list of words. At each step of the loop, we capitalized the word and appended it to the cap
list.
Let’s take another look at when building a list using a for
loop. If we run the following loop:
letters = ["a", "b"]
cap = []
for letter in letters:
cap.append(letter.capitalize())
Before the loop, cap
is empty:
cap = []
On each step of the loop, we access a letter, capitalize it, and append it to the cap
list:
letters = ["a", "b"]
letter = ^
cap = ["A"]
letters = ["a", "b"]
letter = ^
cap = ["A", "B"]
Exercise: looping over a list#
Write a list called numbers
with entries 1, 2, 3, and 4. Use a for
loop to iterate over each number and create a new list called squares
with each entry squared.
# answer here
Another common approach is to loop over indices. Using the range
function, we can loop from 0 to some maximum number.
for i in range(len(words)):
print(words[i])
piano
fountain
hollow
pupil
Here, instead of looping over the words directly, we create an index i
that accesses each part of the words
list. The range(len(words))
part is a common way to loop over all indices in a list.
Let’s look at looping based on an index in more detail. If we run the following loop:
numbers = [1, 2, 3]
for i in range(len(numbers)):
print(i)
Each step of the loop accesses a different index of the list:
numbers = [1, 2, 3]
index = [0, 1, 2]
i = ^
i = 0
numbers = [1, 2, 3]
index = [0, 1, 2]
i = ^
i = 1
numbers = [1, 2, 3]
index = [0, 1, 2]
i = ^
i = 2
Looping over indices is helpful sometimes when we have multiple lists that are related.
participant_id = ["001", "002", "003"]
age = [23, 34, 18]
for i in range(len(participant_id)):
print(participant_id[i], age[i])
001 23
002 34
003 18
Another option in this case is to use the zip
function, which “zips” together two (or more) lists so you can iterate over them together. At each part of the list, you get a tuple of the current element of the two lists.
for p_id, p_age in zip(participant_id, age):
print(p_id, p_age)
001 23
002 34
003 18
Sometimes using zip
results in code that is easier to read compared to using range
.
Let’s look at looping using zip
in more detail. If we run the following loop:
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
for n1, n2 in zip(numbers1, numbers2):
print(n1, n2)
Each step of the loop accesses pairs of the items in the list:
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
n1, n2 = ^
n1 = 1, n2 = 4
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
n1, n2 = ^
n1 = 2, n2 = 5
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
n1, n2 = ^
n1 = 3, n2 = 6
Finally, sometimes it’s helpful to loop over a list while also keeping track of the current index in the list. To do this, we can use enumerate
.
for i, p_id in enumerate(participant_id):
print(p_id, age[i])
001 23
002 34
003 18
The enumerate
function gives us the index first, then the corresponding element of the list.
Let’s look at using enumerate
in more detail. If we run the following loop:
numbers = [1, 2, 3]
for i, n in enumerate(numbers):
print(i, n)
Each step of the loop accesses an index of a list and that part of the list:
numbers = [1, 2, 3]
index = [0, 1, 2]
i, n = ^
i = 0, n = 1
numbers = [1, 2, 3]
index = [0, 1, 2]
i, n = ^
i = 1, n = 2
numbers = [1, 2, 3]
index = [0, 1, 2]
i, n = ^
i = 2, n = 3
Exercise: types of for loops#
Write a list called letters
with entries "a"
, "b"
, "c"
, and "d"
. Write another list called numbers
with entries 1
, 2
, 3
, and 4
.
Write a loop using indexing (for i in range(len(letters))
) and print out each letter.
Write another loop using zip
and print out each letter with the corresponding number.
Write a final loop using enumerate
and print out each index in letters
with the corresponding letter at that index.
# answer here
Advanced: comprehensions#
In Python, comprehensions allow us to run some for
loops in one line, when we just want to generate a new list or dictionary. We won’t use comprehensions much, but they’re used commonly enough in Python that they are good to know about.
List comprehensions#
A common use of for
loops is to generate a list. In this case, we can instead use a list comprehension.
Before, we used a for
loop to capitalize all the items in a list.
words = ["piano", "fountain", "hollow", "pupil"]
cap = []
for word in words:
cap.append(word.capitalize())
print(cap)
['Piano', 'Fountain', 'Hollow', 'Pupil']
Python allows us to write the same thing in one line, using a list comprehension.
cap = [word.capitalize() for word in words]
print(cap)
['Piano', 'Fountain', 'Hollow', 'Pupil']
The first part (word.capitalize()
) indicates how to define each item in the new list. The second part (for word in words
) indicates what we’re looping over, and what each item in the loop should be called. Here, we set each word to the temporary variable word
.
We can also add an if
statement at the end to filter the input list. Only items that return True
will be included.
cap_p = [word.capitalize() for word in words if word.startswith("p")]
print(cap_p)
['Piano', 'Pupil']
List comprehensions take a little getting used to, but can be useful. You can always write out a for
loop instead though!
Dict comprehensions#
A similar method can be used to generate dictionaries using a dict comprehension. For example, say we want to create a dictionary with a key for each index in the list, and the value set to each item in the list.
d = {i: w for i, w in enumerate(words)}
print(d)
{0: 'piano', 1: 'fountain', 2: 'hollow', 3: 'pupil'}
To use this method, we need to generate both the key we want to use for each entry and the corresponding value. We then define each key, value pair to put in the dictionary.
While loops#
Besides for
loops, there is also a different option of while
loops, which can be used when we want to repeat something as long as some condition applies.
One example of using a while
loop is when the user may want to do something multiple times, so that we don’t know in advance how many times we need to run a command. Here, this program prints whatever the user inputs, unless they type quit.
while True:
s = input("Write a word, or 'quit' to stop.")
if s == "quit":
break
print(s)
---------------------------------------------------------------------------
StdinNotImplementedError Traceback (most recent call last)
Cell In[28], line 2
1 while True:
----> 2 s = input("Write a word, or 'quit' to stop.")
3 if s == "quit":
4 break
File /opt/hostedtoolcache/Python/3.12.10/x64/lib/python3.12/site-packages/ipykernel/kernelbase.py:1281, in Kernel.raw_input(self, prompt)
1279 if not self._allow_stdin:
1280 msg = "raw_input was called, but this frontend does not support input requests."
-> 1281 raise StdinNotImplementedError(msg)
1282 return self._input_request(
1283 str(prompt),
1284 self._parent_ident["shell"],
1285 self.get_parent("shell"),
1286 password=False,
1287 )
StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.
The input
function prints some prompt, then takes a string input from the user. The break
statement breaks out of the loop. This can be used in for
loops also, if we want to stop looping earlier, before running out of items to loop over.
Exceptions#
Sometimes a program is unable to run, often because of some problem with the inputs to that function. To deal with these cases, they can signal a problem by raising an exception.
When an exception is raised, it will halt execution of the program. This allows Python programs to stop if some assumption of the program has been violated.
For example, say we need to check that participant IDs are formatted correctly. We can use the .startswith
string method to check if it starts with the correct "sub-"
text. If not, then we raise
an error.
participant_ids = ["sub-001", "sub-002", "sub-003"]
# participant_ids.append("subs-004") # throws an error (try it!)
for pid in participant_ids:
if not pid.startswith("sub-"):
raise ValueError(f"ID does not start with 'sub-': {pid}")
There are many kinds of errors to signal different types of things going wrong. Two common ones are ValueError
(there was some sort of unexpected value for a variable) and IOError
(there was some problem with reading in a file).
We can let a program continue even if an exception is raised by using a try/except
block.
letters = ["a", "b", 2]
cap = []
for letter in letters:
try:
cap.append(letter.capitalize())
except:
continue
print(cap)
['A', 'B']
This lets us “try” some code, see if there is an exception, and if so, run some “backup” code in case an exception has been raised. Here, we just use the continue
keyword to skip to the next loop.
Exercise: exceptions#
Create a variable x = ["a", 1]
. Try running sum(x)
. This will raise an exception because you can’t sum a letter and a number together. Write a try/except
block that attempts to run sum(x)
and prints "Error: cannot calculate sum"
if an exception is raised.
# answer here
Example: putting control flow together#
Control flow becomes much more powerful when we use features together. Let’s go over a commonly used example of control flow: the FizzBuzz game. In this game, you loop over numbers from 1 to 15. For each number, follow these rules:
If a number is divisible by 3, print "Fizz"
.
If a number is divisible by 5, print "Buzz"
.
If a number is divisible by 3 and 5, print "FizzBuzz"
.
def fizzbuzz(n):
for i in range(1, n + 1):
if (i % 3 == 0) and (i % 5 == 0):
print(i, "FizzBuzz")
elif i % 3 == 0:
print(i, "Fizz")
elif i % 5 == 0:
print(i, "Buzz")
fizzbuzz(15)
3 Fizz
5 Buzz
6 Fizz
9 Fizz
10 Buzz
12 Fizz
15 FizzBuzz
Example: using a counter#
Sometimes, it’s useful to have a counter that is updated during a for
loop. For example, say we want to count how many times we get Fizz (a number divisible by 3) in a list of numbers. We first initialize the counter to zero (count = 0
). Then, each time we find that the condition is met, we increment the counter. Here, we use a special syntax for adding to an existing variable, using +=
. The count += 1
expression means the same thing as count = count + 1
, but is easier to write and read.
def count_fizz(n):
count = 0
for i in range(1, n + 1):
if i % 3 == 0:
count += 1
return count
print(count_fizz(15))
print(count_fizz(1000))
5
333
Example: using exceptions#
Sometimes, a function can encounter a problem that means it cannot run. We can raise an exception to deal with this kind of problem.
def count_fizz(n):
if n < 1:
raise ValueError("n cannot be less than 1")
count = 0
for i in range(1, n + 1):
if i % 3 == 0:
count += 1
return count
# count_fizz(0) # this will throw an error (try it!)
Example: using while loops#
Sometimes, we don’t just want to loop over a list. Sometimes we instead need to loop until some condition is met.
For example, say we want to find the first \(n\) times that we get Fizz and return a list of those times.
def list_first_fizz(n):
found = []
i = 1
while len(found) < n:
if i % 3 == 0:
found.append(i)
i += 1
return found
list_first_fizz(5)
[3, 6, 9, 12, 15]
Summary#
Like all programming languages, Python has control flow commands determine what code to run at what times.
if/elif/else
statements run different code depending on what conditions apply.
for
loops iterate over elements and run some code for each element.
while
loops run as long as some condition is met.
exceptions
allow programs to quit if there is some problem that keeps them from being able to do their job.