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 you 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.
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 you 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.
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 you accidentally use = when you mean ==, you 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!
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.
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 ==.
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 you 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 – you have to indent lines to the same level to put them in the same block.
An optional part of an if statement is the else clause. It allows you 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.
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. You can use empty lines (sparingly) to make your code more readable.
The addition of the else keyword allows you to specify actions for the case in which the condition is false. However, there may be cases in which you 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:
You 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 you add a nested if, you have to increase the indentation, so all of your alternatives are indented differently. You 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 you 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.
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 you can achieve something similar by using a dictionary. This example will be clearer when you have read more about dictionaries, but all you need to know for now is that a dictionary is a store of key and value pairs – you retrieve a value by its key, the way you 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)
You 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 you 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
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 your 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.
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 you use it like a boolean, for example as a condition in an if statement. You will almost never have to cast values to bool explicitly. You also don’t have to use the == operator explicitly to check if a variable’s value evaluates to True – you 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 you the answer you expect:
if name == True:
print("Hello, %s!" % name)
Why won’t the last if statement do what you expect? If you 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 you should always use the shorthand syntax, as shown in the first statement – Python will then do the implicit cast for you.
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.
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 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 you a syntax error:
if (x > 3 and < 300): # < 300 is not a valid expression!
x += 1
You 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, you don’t have to use the and operator at all. Instead of writing mark >= 50 and mark < 65 you can simply write 50 <= mark < 65. This doesn’t work in many other languages, but it’s a useful feature of Python.
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 – you can also use it to your 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, you 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.
You 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 you 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 you want to access an object’s attribute or an element from a list or a dictionary, and you 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 in Java 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 suffifent 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 you a syntax error:
if x < 3 or > 300: # > 300 is not a valid expression!
x += 1
# This may not do what you expect:
if x == 2 or 3:
print("x is 2 or 3")
The last example won’t give you 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, 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
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 you mean, even though you can rely on the order of precedence above. Brackets can make complex expressions in your code easier to read and understand, and reduce the opportunity for errors.
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 you 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:
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.
Errors or mistakes in a program are often referred to as bugs. They are almost always the fault of the programmer. The process of finding and eliminating errors is called debugging. Errors can be classified into three major groups:
Python will find these kinds of errors when it tries to parse your program, and exit with an error message without running anything. Syntax errors are mistakes in the use of the Python language, and are analogous to spelling or grammar mistakes in a language like English: for example, the sentence Would you some tea? does not make sense – it is missing a verb.
Common Python syntax errors include:
Python will do its best to tell you where the error is located, but sometimes its messages can be misleading: for example, if you forget to escape a quotation mark inside a string you may get a syntax error referring to a place later in your code, even though that is not the real source of the problem. If you can’t see anything wrong on the line specified in the error message, try backtracking through the previous few lines. As you program more, you will get better at identifying and fixing errors.
Here are some examples of syntax errors in Python:
# missing 'def' keyword in function definition
myfunction(x, y):
return x + y
# 'else' clause without an 'if'
else:
print("Hello!")
# missing colon
if mark >= 50
print("You passed!")
# spelling mistake
if arriving:
print("Hi!")
esle:
print("Bye!")
# indentation mistake
if flag:
print("Flag is set!")
If a program is syntactically correct – that is, free of syntax errors – it will be run by the Python interpreter. However, the program may exit unexpectedly during execution if it encounters a runtime error – a problem which was not detected when the program was parsed, but is only revealed when a particular line is executed. When a program comes to a halt because of a runtime error, we say that it has crashed.
Consider the English instruction flap your arms and fly to Australia. While the instruction is structurally correct and you can understand its meaning perfectly, it is impossible for you to follow it.
Some examples of Python runtime errors:
Runtime errors often creep in if you don’t consider all possible values that a variable could contain, especially when you are processing user input. You should always try to add checks to your code to make sure that it can deal with bad input and edge cases gracefully. We will look at this in more detail in the chapter about exception handling.
Logical errors are the most difficult to fix. They occur when the program runs without crashing, but produces an incorrect result. The error is caused by a mistake in the program’s logic. You won’t get an error message, because no syntax or runtime error has occurred. You will have to find the problem on your own by reviewing all the relevant parts of your code – although some tools can flag suspicious code which looks like it could cause unexpected behaviour.
Sometimes there can be absolutely nothing wrong with your Python implementation of an algorithm – the algorithm itself can be incorrect. However, more frequently these kinds of errors are caused by programmer carelessness. Here are some examples of mistakes which lead to logical errors:
If you misspell an identifier name, you may get a runtime error or a logical error, depending on whether the misspelled name is defined.
A common source of variable name mix-ups and incorrect indentation is frequent copying and pasting of large blocks of code. If you have many duplicate lines with minor differences, it’s very easy to miss a necessary change when you are editing your pasted lines. You should always try to factor out excessive duplication using functions and loops – we will look at this in more detail later.