Selection control statements¶
Introduction¶
In the last chapter, you were introduced to the concept of flow of control: the sequence of statements that the computer executes. In procedurally written code, the computer usually executes instructions in the order that they appear. However, this is not always the case. One of the ways in which programmers can change the flow of control is the use of selection control statements.
In this chapter we will learn about selection statements, which allow a program to choose when to execute certain instructions. For example, a program might choose how to proceed on the basis of the user’s input. As you will be able to see, such statements make a program more versatile.
We will also look at different kinds of programming errors and discuss strategies for finding and correcting them.
Selection: if
statement¶
People make decisions on a daily basis. What should I have for lunch? What should I do this weekend? Every time you make a decision you base it on some criterion. For example, you might decide what to have for lunch based on your mood at the time, or whether you are on some kind of diet. After making this decision, you act on it. Thus decision-making is a two step process – first deciding what to do based on a criterion, and secondly taking an action.
Decision-making by a computer is based on the same two-step process. In Python, decisions are made with the if
statement, also known as the selection statement. When processing an if
statement, the computer first evaluates some criterion or condition. If it is met, the specified action is performed. Here is the syntax for the if
statement:
if condition:
if_body
When it reaches an if
statement, the computer only executes the body of the statement only if the condition is true. Here is an example in Python, with a corresponding flowchart:
if age < 18:
print("Cannot vote")
As we can see from the flowchart, the instructions in the if
body are only executed if the condition is met (i.e. if it is true). If the condition is not met (i.e. false), the instructions in the if
body are skipped.
Relational operators¶
Many if
statements compare two values in order to make a decision. In the last example, we compared the variable age
to the integer 18
to test if age less than 18. We used the operator <
for the comparison. This operator is one of the relational operators that can be used in Python. The table below shows Python’s relational operators.
Operator | Description | Example |
---|---|---|
== | equal to | if (age == 18) |
!= | not equal to | if (score != 10) |
> | greater than | if (num_people > 50) |
< | less than | if (price < 25) |
>= | greater than or equal to | if (total >= 50) |
<= | less than or equal to | if (value <= 30) |
Note that the condition statement can either be true or false. Also note that the operator for equality is ==
– a double equals sign. Remember that =
, the single equals sign, is the assignment operator. If we accidentally use =
when we mean ==
, we are likely to get a syntax error:
>>> if choice = 3:
File "<stdin>", line 1
if choice = 3:
^
SyntaxError: invalid syntax
This is correct:
if choice == 3:
print("Thank you for using this program.")
Note
in some languages, an assignment statement is a valid conditional expression: it is evaluated as true if the assignment is executed successfully, and as false if it is not. In such languages, it is easier to use the wrong operator by accident and not notice!
Value vs identity¶
So far, we have only compared integers in our examples. We can also use any of the above relational operators to compare floating-point numbers, strings and many other types:
# we can compare the values of strings
if name == "Jane":
print("Hello, Jane!")
# ... or floats
if size < 10.5:
print(size)
When comparing variables using ==
, we are doing a value comparison: we are checking whether the two variables have the same value. In contrast to this, we might want to know if two objects such as lists, dictionaries or custom objects that we have created ourselves are the exact same object. This is a test of identity. Two objects might have identical contents, but be two different objects. We compare identity with the is
operator:
a = [1,2,3]
b = [1,2,3]
if a == b:
print("These lists have the same value.")
if a is b:
print("These lists are the same list.")
It is generally the case (with some caveats) that if two variables are the same object, they are also equal. The reverse is not true – two variables could be equal in value, but not the same object.
To test whether two objects are not the same object, we can use the is not
operator:
if a is not b:
print("a and b are not the same object.")
Note
In many cases, variables of built-in immutable types which have the same value will also be identical. In some cases this is because the Python interpreter saves memory (and comparison time) by representing multiple values which are equal by the same object. You shouldn’t rely on this behaviour and make value comparisons using is
– if you want to compare values, always use ==
.
Using indentation¶
In the examples which have appeared in this chapter so far, there has only been one statement appearing in the if
body. Of course it is possible to have more than one statement there; for example:
if choice == 1:
count += 1
print("Thank you for using this program.")
print("Always print this.") # this is outside the if block
The interpreter will treat all the statements inside the indented block as one statement – it will process all the instructions in the block before moving on to the next instruction. This allows us to specify multiple instructions to be executed when the condition is met.
if
is referred to as a compound statement in Python because it combines multiple other statements together. A compound statement comprises one or more clauses, each of which has a header (like if
) and a suite (which is a list of statements, like the if
body). The contents of the suite are delimited with indentation – we have to indent lines to the same level to put them in the same block.
The else
clause¶
An optional part of an if statement is the else
clause. It allows us to specify an alternative instruction (or set of instructions) to be executed if the condition is not met:
if condition:
if_body
else:
else_body
To put it another way, the computer will execute the if
body if the condition is true, otherwise it will execute the else
body. In the example below, the computer will add 1 to x if it is zero, otherwise it will subtract 1 from x:
if x == 0:
x += 1
else:
x -= 1
This flowchart represents the same statement:
The computer will execute one of the branches before proceeding to the next instruction.
Exercise 1¶
Which of these fragments are valid and invalid first lines of
if
statements? Explain why:if (x > 4)
if x == 2
if (y =< 4)
if (y = 5)
if (3 <= a)
if (1 - 1)
if ((1 - 1) <= 0)
if (name == "James")
What is the output of the following code fragment? Explain why.
x = 2 if x > 3: print("This number") print("is greater") print("than 3.")
How can we simplify these code fragments?
if bool(a) == True: print("a is true")
if x > 50: b += 1 a = 5 else: b -= 1 a = 5
More on the if
statement¶
Nested if
statements¶
In some cases you may want one decision to depend on the result of an earlier decision. For example, you might only have to choose which shop to visit if you decide that you are going to do your shopping, or what to have for dinner after you have made a decision that you are hungry enough for dinner.
In Python this is equivalent to putting an if
statement within the body of either the if
or the else
clause of another if
statement. The following code fragment calculates the cost of sending a small parcel. The post office charges R5 for the first 300g, and R2 for every 100g thereafter (rounded up), up to a maximum weight of 1000g:
if weight <= 1000:
if weight <= 300:
cost = 5
else:
cost = 5 + 2 * round((weight - 300)/100)
print("Your parcel will cost R%d." % cost)
else:
print("Maximum weight for small parcel exceeded.")
print("Use large parcel service instead.")
Note that the bodies of the outer if
and else
clauses are indented, and the bodies of the inner if
and else
clauses are indented one more time. It is important to keep track of indentation, so that each statement is in the correct block. It doesn’t matter that there’s an empty line between the last line of the inner if
statement and the following print statement – they are still both part of the same block (the outer if
body) because they are indented by the same amount. We can use empty lines (sparingly) to make our code more readable.
The elif
clause and if
ladders¶
The addition of the else keyword allows us to specify actions for the case in which the condition is false. However, there may be cases in which we would like to handle more than two alternatives. For example, here is a flowchart of a program which works out which grade should be assigned to a particular mark in a test:
We should be able to write a code fragment for this program using nested if statements. It might look something like this:
if mark >= 80:
grade = A
else:
if mark >= 65:
grade = B
else:
if mark >= 50:
grade = C
else:
grade = D
This code is a bit difficult to read. Every time we add a nested if
, we have to increase the indentation, so all of our alternatives are indented differently. We can write this code more cleanly using elif
clauses:
if mark >= 80:
grade = A
elif mark >= 65:
grade = B
elif mark >= 50:
grade = C
else:
grade = D
Now all the alternatives are clauses of one if
statement, and are indented to the same level. This is called an if ladder. Here is a flowchart which more accurately represents this code:
The default (catch-all) condition is the else
clause at the end of the statement. If none of the conditions specified earlier is matched, the actions in the else
body will be executed. It is a good idea to include a final else
clause in each ladder to make sure that we are covering all cases, especially if there’s a possibility that the options will change in the future. Consider the following code fragment:
if course_code == "CSC":
department_name = "Computer Science"
elif course_code == "MAM":
department_name = "Mathematics and Applied Mathematics"
elif course_code == "STA":
department_name = "Statistical Sciences"
else:
department_name = None
print("Unknown course code: %s" % course_code)
if department_name:
print("Department: %s" % department_name)
What if we unexpectedly encounter an informatics course, which has a course code of "INF"
? The catch-all else
clause will be executed, and we will immediately see a printed message that this course code is unsupported. If the else
clause were omitted, we might not have noticed that anything was wrong until we tried to use department_name
and discovered that it had never been assigned a value. Including the else
clause helps us to pick up potential errors caused by missing options early.
Boolean values, operators and expressions¶
The bool
type¶
In Python there is a value type for variables which can either be true or false: the boolean type, bool
. The true value is True
and the false value is False
. Python will implicitly convert any other value type to a boolean if we use it like a boolean, for example as a condition in an if
statement. We will almost never have to cast values to bool
explicitly. We also don’t have to use the ==
operator explicitly to check if a variable’s value evaluates to True
– we can use the variable name by itself as a condition:
name = "Jane"
# This is shorthand for checking if name evaluates to True:
if name:
print("Hello, %s!" % name)
# It means the same thing as this:
if bool(name) == True:
print("Hello, %s!" % name)
# This won't give us the answer we expect:
if name == True:
print("Hello, %s!" % name)
Why won’t the last if
statement do what we expect? If we cast the string "Jane"
to a boolean, it will be equal to True
, but it isn’t equal to True
while it’s still a string – so the condition in the last if
statement will evaluate to False
. This is why we should always use the shorthand syntax, as shown in the first statement – Python will then do the implicit cast for us.
Note
For historical reasons, the numbers 0
and 0.0
are actually equal to False
and 1
and 1.0
are equal to True
. They are not, however, identical objects – you can test this by comparing them with the is
operator.
At the end of the previous chapter, we discussed how Python converts values to booleans implicitly. Remember that all non-zero numbers and all non-empty strings are True
and zero and the empty string (""
) are False
. Other built-in data types that can be considered to be “empty” or “not empty” follow the same pattern.
Boolean operations¶
Decisions are often based on more than one factor. For example, you might decide to buy a shirt only if you like it AND it costs less than R100. Or you might decide to go out to eat tonight if you don’t have anything in the fridge OR you don’t feel like cooking. You can also alter conditions by negating them – for example you might only want to go to the concert tomorrow if it is NOT raining. Conditions which consist of simpler conditions joined together with AND, OR and NOT are referred to as compound conditions. These operators are known as boolean operators.
The and
operator¶
The AND operator in Python is and
. A compound expression made up of two subexpressions and the and
operator is only true when both subexpressions are true:
if mark >= 50 and mark < 65:
print("Grade B")
The compound condition is only true if the given mark is less than 50 and it is less than 65. The and
operator works in the same way as its English counterpart. We can define the and
operator formally with a truth table such as the one below. The table shows the truth value of a and b
for every possible combination of subexpressions a
and b
. For example, if a
is true and b
is true, then a and b
is true.
a |
b |
a and b |
---|---|---|
True |
True |
True |
True |
False |
False |
False |
True |
False |
False |
False |
False |
and
is a binary operator so it must be given two operands. Each subexpression must be a valid complete expression:
# This is correct:
if (x > 3 and x < 300):
x += 1
# This will give us a syntax error:
if (x > 3 and < 300): # < 300 is not a valid expression!
x += 1
We can join three or more subexpressions with and
– they will be evaluated from left to right:
condition_1 and condition_2 and condition_3 and condition_4
# is the same as
((condition_1 and condition_2) and condition_3) and condition_4
Note
for the special case of testing whether a number falls within a certain range, we don’t have to use the and
operator at all. Instead of writing mark >= 50 and mark < 65
we can simply write 50 <= mark < 65
. This doesn’t work in many other languages, but it’s a useful feature of Python.
Short-circuit evaluation¶
Note that if a
is false, the expression a and b
is false whether b
is true or not. The interpreter can take advantage of this to be more efficient: if it evaluates the first subexpression in an AND expression to be false, it does not bother to evaluate the second subexpression. We call and
a shortcut operator or short-circuit operator because of this behaviour.
This behaviour doesn’t just make the interpreter slightly faster – we can also use it to our advantage when writing programs. Consider this example:
if x > 0 and 1/x < 0.5:
print("x is %f" % x)
What if x is zero? If the interpreter were to evaluate both of the subexpressions, we would get a divide by zero error. But because and
is a short-circuit operator, the second subexpression will only be evaluated if the first subexpression is true. If x is zero, it will evaluate to false, and the second subexpression will not be evaluated at all.
We could also have used nested if
statements, like this:
if x > 0:
if 1/x < 0.5:
print("x is %f" % x)
Using and
instead is more compact and readable – especially if we have more than two conditions to check. These two snippets do the same thing:
if x != 0:
if y != 0:
if z != 0:
print(1/(x*y*z))
if x != 0 and y != 0 and z != 0:
print(1/(x*y*z))
This often comes in useful if we want to access an object’s attribute or an element from a list or a dictionary, and we first want to check if it exists:
if hasattr(my_person, "name") and len(myperson.name) > 30:
print("That's a long name, %s!" % myperson.name)
if i < len(mylist) and mylist[i] == 3:
print("I found a 3!")
if key in mydict and mydict[key] == 3:
print("I found a 3!")
The or
operator¶
The OR operator in Python is or
. A compound expression made up of two subexpressions and the or
operator is true when at least one of the subexpressions is true. This means that it is only false in the case where both subexpressions are false, and is true for all other cases. This can be seen in the truth table below:
a |
b |
a or b |
---|---|---|
True |
True |
True |
True |
False |
True |
False |
True |
True |
False |
False |
False |
The following code fragment will print out a message if the given age is less than 0 or if it is more than 120:
if age < 0 or age > 120:
print("Invalid age: %d" % age)
The interpreter also performs a short-circuit evaluation for or
expressions. If it evaluates the first subexpression to be true, it will not bother to evaluate the second, because this is sufficient to determine that the whole expression is true.
The || operator is also binary:
# This is correct:
if x < 3 or x > 300:
x += 1
# This will give us a syntax error:
if x < 3 or > 300: # > 300 is not a valid expression!
x += 1
# This may not do what we expect:
if x == 2 or 3:
print("x is 2 or 3")
The last example won’t give us an error, because 3
is a valid subexpression – and since it is a non-zero number it evaluates to True
. So the last if
body will always execute, regardless of the value of x
!
The not
operator¶
The NOT operator, not
in Python, is a unary operator: it only requires one operand. It is used to reverse an expression, as shown in the following truth table:
a |
not a |
---|---|
True |
False |
False |
True |
The not
operator can be used to write a less confusing expression. For example, consider the following example in which we want to check whether a string doesn’t start with “A”:
if name.startswith("A"):
pass # a statement body can't be empty -- this is an instruction which does nothing.
else:
print("'%s' doesn't start with A!" % s)
# That's a little clumsy -- let's use "not" instead!
if not name.startswith("A"):
print("'%s' doesn't start with A!" % s)
Here are a few other examples:
# Do something if a flag is False:
if not my_flag:
print("Hello!")
# This...
if not x == 5:
x += 1
# ... is equivalent to this:
if x != 5:
x += 1
Precedence rules for boolean expressions¶
Here is a table indicating the relative level of precedence for all the operators we have seen so far, including the arithmetic, relational and boolean operators.
Operators |
---|
() (highest) |
** |
*, /, % |
+, - |
<, <=, >, >= ==, != |
is, is not |
not |
and |
or (lowest) |
It is always a good idea to use brackets to clarify what we mean, even though we can rely on the order of precedence above. Brackets can make complex expressions in our code easier to read and understand, and reduce the opportunity for errors.
DeMorgan’s law for manipulating boolean expressions¶
The not
operator can make expressions more difficult to understand, especially if it is used multiple times. Try only to use the not
operator where it makes sense to have it. Most people find it easier to read positive statements than negative ones. Sometimes we can use the opposite relational operator to avoid using the not
operator, for example:
if not mark < 50:
print("You passed")
# is the same as
if mark >= 50:
print("You passed")
This table shows each relational operator and its opposite:
Operator | Opposite |
---|---|
== |
!= |
> |
<= |
< |
>= |
There are other ways to rewrite boolean expressions. The 19th-century logician DeMorgan proved two properties of negation that we can use.
Consider this example in English: it is not both cool and rainy today. Does this sentence mean the same thing as it is not cool and not rainy today? No, the first sentence says that both conditions are not true, but either one of them could be true. The correct equivalent sentence is it is not cool or not rainy today.
We have just used DeMorgan’s law to distribute NOT over the first sentence. Formally, DeMorgan’s laws state:
- NOT (a AND b) = (NOT a) OR (NOT b)
- NOT (a OR b) = (NOT a) AND (NOT b)
We can use these laws to distribute the not
operator over boolean expressions in Python. For example:
if not (age > 0 and age <= 120):
print("Invalid age")
# can be rewritten as
if age <= 0 or age > 120:
print("Invalid age")
Instead of negating each operator, we used its opposite, eliminating not
altogether.
Exercise 2¶
For what values of
input
will this program print"True"
?if not input > 5: print("True")
For what values of
absentee_rate
andoverall_mark
will this program print"You have passed the course."
?if absentee_rate <= 5 and overall_mark >= 50: print("You have passed the course.")
For what values of
x
will this program print"True"
?if x > 1 or x <= 8: print("True")
Eliminate
not
from each of these boolean expressions:not total <= 2 not count > 40 not (value > 20.0 and total != 100.0) not (angle > 180 and width == 5) not (count == 5 and not (value != 10) or count > 50) not (value > 200 or value < 0 and not total == 0)
The None value¶
We often initialise a number to zero or a string to an empty string before we give it a more meaningful value. Zero and various “empty” values evaluate to False
in boolean expressions, so we can check whether a variable has a meaningful value like this:
- if (my_variable):
- print(my_variable)
Sometimes, however, a zero or an empty string is a meaningful value. How can we indicate that a variable isn’t set to anything if we can’t use zero or an empty string? We can set it to None
instead.
In Python, None
is a special value which means “nothing”. Its type is called NoneType
, and only one None
value exists at a time – all the None
values we use are actually the same object:
print(None is None) # True
None
evaluates to False
in boolean expressions. If we don’t care whether our variable is None
or some other value which is also false, we can just check its value like this:
if my_string:
print("My string is '%s'." % my_string)
If, however, we want to distinguish between the case when our variable is None
and when it is empty (or zero, or some other false value) we need to be more specific:
if my_number is not None:
print(my_number) # could still be zero
if my_string is None:
print("I haven't got a string at all!")
elif not my_string: # another false value, i.e. an empty string
print("My string is empty!")
else:
print("My string is '%s'." % my_string)
Switch statements and dictionary-based dispatch¶
if
ladders can get unwieldy if they become very long. Many languages have a control statement called a switch, which tests the value of a single variable and makes a decision on the basis of that value. It is similar to an if
ladder, but can be a little more readable, and is often optimised to be faster.
Python does not have a switch statement, but we can achieve something similar by using a dictionary. This example will be clearer when we have read more about dictionaries, but all we need to know for now is that a dictionary is a store of key and value pairs – we retrieve a value by its key, the way we would retrieve a list element by its index. Here is how we can rewrite the course code example:
DEPARTMENT_NAMES = {
"CSC": "Computer Science",
"MAM": "Mathematics and Applied Mathematics",
"STA": "Statistical Sciences", # Trailing commas like this are allowed in Python!
}
if course_code in DEPARTMENT_NAMES: # this tests whether the variable is one of the dictionary's keys
print("Department: %s" % DEPARTMENT_NAMES[course_code])
else:
print("Unknown course code: %s" % course_code)
We are not limited to storing simple values like strings in the dictionary. In Python, functions can be stored in variables just like any other object, so we can even use this dispatch method to execute completely different statements in response to different values:
def reverse(string):
print("'%s' reversed is '%s'." % (string, string[::-1]))
def capitalise(string):
print("'%s' capitalised is '%s'." % (string, string.upper()))
ACTIONS = {
"r": reverse, # use the function name without brackets to refer to the function without calling it
"c": capitalise,
}
my_function = ACTIONS[my_action] # now we retrieve the function
my_function(my_string) # and now we call it
The conditional operator¶
Python has another way to write a selection in a program – the conditional operator. It can be used within an expression (i.e. it can be evaluated) – in contrast to if
and if
-else
, which are just statements and not expressions. It is often called the ternary operator because it has three operands (binary operators have two, and unary operators have one). The syntax is as follows:
true expression if
condition else
false expression
For example:
result = "Pass" if (score >= 50) else "Fail"
This means that if score
is at least 50, result
is assigned "Pass"
, otherwise it is assigned "Fail"
. This is equivalent to the following if
statement:
- if (score >= 50):
- result = “Pass”
- else:
- result = “Fail”
The ternary operator can make simple if
statements shorter and more legible, but some people may find this code harder to understand. There is no functional or efficiency difference between a normal if
-else
and the ternary operator. You should use the operator sparingly.
Exercise 3¶
Rewrite the following fragment as an if-ladder (using
elif
statements):if temperature < 0: print("Below freezing") else: if temperature < 10: print("Very cold") else: if temperature < 20: print(Chilly) else: if temperature < 30: print("Warm") else: if temperature < 40: print("Hot") else: print("Too hot")
Write a Python program to assign grades to students at the end of the year. The program must do the following:
Ask for a student number.
Ask for the student’s tutorial mark.
Ask for the student’s test mark.
Calculate whether the student’s average so far is high enough for the student to be permitted to write the examination. If the average (mean) of the tutorial and test marks is lower than 40%, the student should automatically get an F grade, and the program should print the grade and exit without performing the following steps.
Ask for the student’s examination mark.
Calculate the student’s final mark. The tutorial and test marks should count for 25% of the final mark each, and the final examination should count for the remaining 50%.
Calculate and print the student’s grade, according to the following table:
Weighted final score Final grade 80 <= mark <= 100 A 70 <= mark < 80 B 60 <= mark < 70 C 50 <= mark < 60 D mark < 50 E
Answers to exercises¶
Answer to exercise 1¶
if (x > 4)
– validif x == 2
– valid (brackets are not compulsory)if (y =< 4)
– invalid (=<
is not a valid operator; it should be<=
)if (y = 5)
– invalid (=
is the assignment operator, not a comparison operator)if (3 <= a)
– validif (1 - 1)
– valid (1 - 1
evaluates to zero, which is false)if ((1 - 1) <= 0)
– validif (name == "James")
– valid
- The program will print out:
- is greaterthan 3.
This happens because the last two print statements are not indented – they are outside the
if
statement, which means that they will always be executed.We don’t have to compare variables to boolean values and compare them to
True
explicitly. This will be done implicitly if we just evaluate the variable in the condition of theif
statement:if a: print("a is true")
We set
a
to the same value whether we execute theif
block or theelse
block, so we can move this line outside theif
statement and only write it once.if x > 50: b += 1 else: b -= 1 a = 5
Answer to exercise 2¶
The program will print
"True"
ifinput
is less than or equal to 5.The program will print
"You have passed the course."
ifabsentee_rate
is less than or equal to5
andoverall_mark
is greater than or equal to50
.The program will print
"True"
for any value ofx
.total > 2 count <= 40 value <= 20.0 or total == 100.0 angle <= 180 or width != 5 (count != 5 or value != 10) and count <= 50 value <= 200 and (value >= 0 or total == 0)
Answer to exercise 3¶
if temperature < 0: print("Below freezing") elif temperature < 10: print("Very cold") elif temperature < 20: print(Chilly) elif temperature < 30: print("Warm") elif temperature < 40: print("Hot") else: print("Too hot")
Here is an example program:
student_number = input("Please enter a student number: ") tutorial_mark = float(input("Please enter the student's tutorial mark: ")) test_mark = float(input("Please enter the student's test mark: ")) if (tutorial_mark + test_mark) / 2 < 40: grade = "F" else: exam_mark = float(input("Please enter the student's final examination mark: ")) mark = (tutorial_mark + test_mark + 2 * exam_mark) / 4 if 80 <= mark <= 100: grade = "A" elif 70 <= mark < 80: grade = "B" elif 60 <= mark < 70: grade = "C" elif 50 <= mark < 60: grade = "D" else: grade = "E" print "%s's grade is %s." % (student_number, grade)