5. Conditionals

5.1. The modulus operator

The modulus operator works on integers (and integer expressions) and yields the remainder when the first operand is divided by the second. In Python, the modulus operator is a percent sign (%). The syntax is the same as for other operators:

>>> quotient = 7 // 3     # This is the integer division operator
>>> print(quotient)
2
>>> remainder = 7 % 3
>>> print(remainder)
1

So 7 divided by 3 is 2 with 1 left over.

The modulus operator turns out to be surprisingly useful. For example, you can check whether one number is divisible by another—if x % y is zero, then x is divisible by y.

Also, you can extract the right-most digit or digits from a number. For example, x % 10 yields the right-most digit of x (in base 10). Similarly x % 100 yields the last two digits.

It is also extremely useful for doing conversions, say from seconds, to hours, minutes and seconds. So let’s write a program to ask the user to enter some seconds, and we’ll convert them into hours, minutes, and remaining seconds.

total_secs = int(input("How many seconds, in total?"))
hours = total_secs // 3600
secs_still_remaining = total_secs % 3600
minutes =  secs_still_remaining // 60
secs_finally_remaining = secs_still_remaining  % 60

print("Hrs=", hours, "  mins=", minutes,  "secs=", secs_finally_remaining)

5.2. Boolean values and expressions

The Python type for storing true and false values is called bool, named after the British mathematician, George Boole. George Boole created Boolean algebra, which is the basis of all modern computer arithmetic.

There are only two boolean values: True and False. Capitalization is important, since true and false are not boolean values in Python.

>>> type(True)
<class 'bool'>
>>> type(true)
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
NameError: name 'true' is not defined

A boolean expression is an expression that evaluates to a boolean value. The operator == compares two values and produces a boolean value:

>>> 5 == 5
True
>>> 5 == 6
False

In the first statement, the two operands are equal, so the expression evaluates to True; in the second statement, 5 is not equal to 6, so we get False.

The == operator is one of six common comparison operators; the others are:

x != y               # x is not equal to y
x > y                # x is greater than y
x < y                # x is less than y
x >= y               # x is greater than or equal to y
x <= y               # x is less than or equal to y

Although these operations are probably familiar to you, the Python symbols are different from the mathematical symbols. A common error is to use a single equal sign (=) instead of a double equal sign (==). Remember that = is an assignment operator and == is a comparison operator. Also, there is no such thing as =< or =>.

5.3. Logical operators

There are three logical operators: and, or, and not. The semantics (meaning) of these operators is similar to their meaning in English. For example, x > 0 and x < 10 is true only if x is greater than 0 and at the same time, x is less than 10.

n % 2 == 0 or n % 3 == 0 is true if either of the conditions is true, that is, if the number is divisible by 2 or divisible by 3.

Finally, the not operator negates a boolean expression, so not(x > y) is true if (x > y) is false, that is, if x is less than or equal to y.

5.4. Conditional execution

In order to write useful programs, we almost always need the ability to check conditions and change the behavior of the program accordingly. Conditional statements give us this ability. The simplest form is the if statement:

if x % 2 == 0:
    print(x, " is even")
else:
    print(x, " is odd")

The boolean expression after the if statement is called the condition. If it is true, then the indented statements get executed. If not, then the statements indented under the else clause get executed.

The syntax for an if statement looks like this:

if BOOLEAN EXPRESSION:
    STATEMENTS_1        # executed if condition evaluates to True
else:
    STATEMENTS_2        # executed if condition evaluates to False

As with the function definition from the last chapter and other compound statements like for, the if statement consists of a header line and a body. The header line begins with the keyword if followed by a boolean expression and ends with a colon (:).

The indented statements that follow are called a block. The first unindented statement marks the end of the block.

Each of the statements inside the first block of statements are executed in order if the boolean expression evaluates to True. The entire first block of statements is skipped if the boolean expression evaluates to False, and instead all the statements under the else clause are executed.

There is no limit on the number of statements that can appear under the two clauses of an if statement, but there has to be at least one statement in each block. Occasionally, it is useful to have a section with no statements (usually as a place keeper, or scaffolding, for code you haven’t written yet). In that case, you can use the pass statement, which does nothing except act as a placeholder.

if True:          # This is always true
    pass          # so this is always executed, but it does nothing
else:
    pass

5.5. Omitting the else clause

Another form of the if statement is one in which the else clause is omitted entirely. In this case, when the condition evaluates to True, the statements are executed, otherwise the flow of execution continues to the statement after the if.

if x < 0:
    print("The negative number ",  x, " is not valid here.")

Python terminology

Python documentation sometimes uses the term suite of statements to mean what we have called a block here. They mean the same thing, and since most other languages and computer scientists use the word block, we’ll stick with that.

Notice too that else is not a statement. The if statement has two clauses, one of which is the (optional) else clause.

5.6. Chained conditionals

Sometimes there are more than two possibilities and we need more than two branches. One way to express a computation like that is a chained conditional:

if x < y:
    STATEMENTS_A
elif x > y:
    STATEMENTS_B
else:
    STATEMENTS_C

Flowchart of this chained conditional

_images/flowchart_chained_conditional.png

elif is an abbreviation of else if. Again, exactly one branch will be executed. There is no limit of the number of elif statements but only a single (and optional) final else statement is allowed and it must be the last branch in the statement:

if choice == 'a':
    function_a()
elif choice == 'b':
    function_b()
elif choice == 'c':
    function_c()
else:
    print("Invalid choice.")

Each condition is checked in order. If the first is false, the next is checked, and so on. If one of them is true, the corresponding branch executes, and the statement ends. Even if more than one condition is true, only the first true branch executes.

5.7. Nested conditionals

One conditional can also be nested within another. (It is the same theme of composibility, again!) We could have written the previous example as follows:

if x < y:
    STATEMENTS_A
else:
    if x > y:
        STATEMENTS_B
    else:
        STATEMENTS_C

The outer conditional contains two branches. The second branch contains another if statement, which has two branches of its own. Those two branches could contain conditional statements as well.

Although the indentation of the statements makes the structure apparent, nested conditionals very quickly become difficult to read. In general, it is a good idea to avoid them when you can.

Logical operators often provide a way to simplify nested conditional statements. For example, we can rewrite the following code using a single conditional:

if 0 < x:            # assume x is an int here
    if x < 10:
        print("x is a positive single digit.")

The print function is called only if we make it past both the conditionals, so we can use the and operator:

if 0 < x and x < 10:
    print("x is a positive single digit.")

5.8. More about the return statement

The return statement – with or without a value – allows you to terminate the execution of a function before you reach the end of a sequence of instructions. One reason to use it is if you detect an error condition:

def print_square_root(x):
    if x <= 0:
        print("Positive numbers only, please.")
        return

    result = x**0.5
    print("The square root of", x, "is", result)

The function print_square_root has a parameter named x. The first thing it does is check whether x is less than or equal to 0, in which case it displays an error message and then uses return to exit the function. The flow of execution immediately returns to the caller, and the remaining lines of the function are not executed.

Sometimes it is useful to have multiple return statements, one in each branch of a conditional. We have already seen the built-in abs, now we see how to write our own:

def absolute_value(x):
    if x < 0:
        return -x
    else:
        return x

Another way to write the above function is to leave out the else and just follow the if condition by the second return statement.

def absolute_value(x):
    if x < 0:
        return -x
    return x

Think about this version and convince yourself it works the same as the first one.

Code that appears after a return statement, or any other place the flow of execution can never reach, is called dead code, or unreachable code.

In a fruitful function, it is a good idea to ensure that every possible path through the program hits a return statement. The following version of absolute_value fails to do this:

def absolute_value(x):
    if x < 0:
        return -x
    elif x > 0:
        return x

This version is not correct because if x happens to be 0, neither condition is true, and the function ends without hitting a return statement. In this case, the return value is a special value called None:

>>> print(absolute_value(0))
None

All Python functions return None whenever they do not return another value.

It is also possible to use a return statement in the middle of a for loop. The common pattern is to have some condition for which your program is checking as it loops over a collection of values. When you find what you’re looking for, you want to stop immediately.

An if statement checks to see if you’ve found what you seek, and a return statement stops the function immediately sends back the result once you have.

Here is an example that returns the first even number is a list of numbers. If there is no even number in the list, it returns None:

def find_even_number(number_list):
    for number in number_list:
        if number % 2 == 0:
            return number 

If we put this function in a module named numbers.py, we can import it and test it in the shell:

>>> from numbers import find_even_number
>>> find_even_number([1, 3, 6, 11, 13, 22, 35])
6
>>> find_even_number([1, 3, 7, 11, 13, 22, 35])
22
>>> find_even_number([1, 3, 7, 11, 13, 23, 35])
>>>

Single-step through this function with these example calls and convince yourself that in the first example the function returns while processing the third element in the list in the – it does not have to traverse the whole list.

It returns on the 2nd to last element in the second example. What happens in the third example? What gets returned?

Try this to find out:

>>> print(find_even_number([1, 3, 7, 11, 13, 23, 35]))

5.9. A Turtle Bar Chart

We will now use the turtle module to get tess to draw a bar chart.

The turtle module has a lot more power than we’ve seen so far. If you want to see the full documentation, look at http://docs.python.org/py3k/library/turtle.html.

Here are a couple of new turtle tricks we will need for the current task:

  • We can get a turtle to display text on the canvas at the turtle’s current position. The method is tess.write("Hello").
  • We can fill a shape (circle, semicircle, triangle, etc.) with a fill color. It is a two-step process. First we call the method tess.begin_fill(), then we draw the shape, and call tess.end_fill() to fill it in.
  • We’ve previously set the color of our turtle - we can also set its fill-color, which need not be the same as the turtle and the pen color. We use tess.color("blue","red") to set the turtle to draw in blue, and fill in red.

Since a bar chart is made up of bars with heights corresponding to the value of the datum being graphed, let’s start with a function to draw a single bar:

def draw_bar(t, height):
    """ Get turtle t to draw one bar, of height. """
    t.setheading(90)         # face the turtle up
    t.forward(height)        # draw up the left side
    t.right(90)
    t.forward(40)            # width of bar, along the top
    t.right(90)
    t.forward(height)        # down again!

Our plan will be to call draw_bar repeatly with the data being graphed to make our bar chart.

But first, let’s use what know about the import statement to make using the make_window and make_turtle functions we wrote last chapter easier.

Create a module named turtletools.py that contains these functions:

import turtle


def make_window(color, title):
    """
      Set up the window with the given background color and title.
      Returns the new window.
    """
    turtle.setup(800, 600)
    w = turtle.Screen()
    w.bgcolor(color)
    w.title(title)
    return w


def make_turtle(color, fillcolor, size, shape):
    """
      Set up a turtle with the given color and pensize.
      Returns the new turtle.
    """
    t = turtle.Turtle()
    t.color(color, fillcolor)
    t.pensize(size)
    t.shape(shape)
    return t

Notice that this new module begins by importing the turtle module. It is common for one module to import others.

We’ve added a new parameter, fillcolor, to make_turtle so that we can set the fill color of our turtles.

Now we can put these parts together in a program named tess_bar_1.py that will draw a bar chart:

import turtletools


def draw_bar(t, height):
    """ Get turtle t to draw one bar, of height. """
    t.setheading(90)         # face the turtle up
    t.forward(height)        # draw up the left side
    t.right(90)
    t.forward(40)            # width of bar, along the top
    t.right(90)
    t.forward(height)        # down again!


# use turtletools module to get a window and a turtle
wn = turtletools.make_window("lightgreen", "Bar Graph 1")
tess = turtletools.make_turtle("blue", "red", 3, "arrow")

# a list of values to graph
values = [48, 117, 200, 240, 160, 260, 220]

# move tess to position the bar chart
tess.penup()
tess.setpos(-300, -200)
tess.pendown()

# loop over the values in the list, calling draw_bar to graph each one
for value in values:
    draw_bar(tess, value)

wn.exitonclick()

Note

We did not need to import the turtle module directly, since it is imported inside turtletools.

Running the program produces this bar graph:

_images/tess_bar_1.png

A recurring theme here is the mental chunking, or how we brake a large problem into smaller pieces so that we can solve each piece seperately.

One chunk here is to draw a single bar, and we wrote a function to do that. Then, for the whole chart, we repeatedly called our function.

We also reused our previous work to simplify the creation of windows and turtles by placing these tools in a module of our own making.

Next, at the top of each bar, we’ll print the value of the data. We’ll do this in the body of draw_bar, by adding t.write('  ' + str(height)) immediately after the first t.forward(height) statement. We’ve put two spaces in front of the number, and turned the number into a string. Without this extra space the text would be cramped awkwardly against the bar to the left.

Our improved version looks like this:

_images/tess_bar_2.png

Lastly, we’ll add two lines to fill each bar. Our final program, in tess_bar_3.py now looks like this:

import turtletools


def draw_bar(t, height):
    """ Get turtle t to draw one bar, of height. """
    t.setheading(90)            # face the turtle up
    t.begin_fill()
    t.forward(height)           # draw up the left side
    t.write('  ' + str(height)) # write the value at the top of the bar
    t.right(90)
    t.forward(40)               # width of bar, along the top
    t.right(90)
    t.forward(height)           # down again!
    t.end_fill()


wn = turtletools.make_window("lightgreen", "Bar Graph 3")
tess = turtletools. make_turtle("blue", "red", 3, "arrow")
values = [48, 117, 200, 240, 160, 260, 220]

tess.penup()
tess.setpos(-300, -200)
tess.pendown()

for value in values:
    draw_bar(tess, value)

wn.exitonclick()

It produces the following, which is more visually satisfying:

_images/tess_bar_3.png

5.10. Glossary

block
A group of consecutive statements with the same indentation.
body
The block of statements in a compound statement that follows the header.
boolean expression
An expression that is either true or false.
boolean value
There are exactly two boolean values: True and False. Boolean values result when a boolean expression is evaluated by the Python interepreter. They have type bool.
branch
One of the possible paths of the flow of execution determined by conditional execution.
chained conditional
A conditional branch with more than two possible flows of execution. In Python chained conditionals are written with if ... elif ... else statements.
comparison operator
One of the operators that compares two values: ==, !=, >, <, >=, and <=.
condition
The boolean expression in a conditional statement that determines which branch is executed.
conditional statement
A statement that controls the flow of execution depending on some condition. In Python the keywords if, elif, and else are used for conditional statements.
logical operator
One of the operators that combines boolean expressions: and, or, and not.
modulus operator
An operator, denoted with a percent sign ( %), that works on integers and yields the remainder when one number is divided by another.
nesting
One program structure within another, such as a conditional statement inside a branch of another conditional statement.
prompt
A visual cue that tells the user to input data.
type conversion
An explicit function call that takes a value of one type and computes a corresponding value of another type.
wrapping code in a function
The process of adding a function header and parameters to a sequence of program statements is often refered to as “wrapping the code in a function”. This process is very useful whenever the program statements in question are going to be used multiple times. It is even more useful when it allows the programmer to express their mental chunking, and how they’ve broken a complex problem into pieces.

5.11. Exercises

  1. Evaluate the following numerical expressions in your head, then use the Python interpreter to check your results:

    1. >>> 5 % 2
    2. >>> 9 % 5
    3. >>> 15 % 12
    4. >>> 12 % 15
    5. >>> 6 % 6
    6. >>> 0 % 7
    7. >>> 7 % 0

    What happened with the last example? Why? If you were able to correctly anticipate the computer’s response in all but the last one, it is time to move on. If not, take time now to make up examples of your own. Explore the modulus operator until you are confident you understand how it works.

  2. You look at the clock and it is exactly 2 pm. You set an alarm to go off in 51 hours. At what time does the alarm go off?

  3. Write a Python program to solve the general version of the above problem. Ask the user for the time now (in hours), and ask for the number of hours to wait. Your program should output what the time will be on the clock when the alarm goes off.

  4. Assume the days of the week are numbered 0, 1, 2, 3, 4, 5, 6 from Sunday to Saturday. Write a function which is given the day number, and it returns the day name (a string).

  5. You go on a wonderful holiday (perhaps to jail, if you don’t like happy exercises) leaving on day number 3 (a Wednesday). You return home after 137 sleeps.

    Write a general version of the program which asks for the starting day number, and the length of your stay, and it will tell you the name of day of the week you will return on.

  6. Give the logical opposites of these conditions

    1. a > b
    2. a >= b
    3. a >= 18  and  day == 3
    4. a >= 18  and  day != 3
  7. What do these expressions evaluate to?

    1. 3 == 3
    2. 3 != 3
    3. 3 >= 4
    4. not (3 < 4)
  8. Write a function which is given an exam mark, and it returns a string – the grade for that mark — according to this scheme:

    Mark

    Grade

    >= 75

    First

    [70-75)

    Upper Second

    [60-70)

    Second

    [50-60)

    Third

    [45-50)

    F1 Supp

    [40-45)

    F2

    < 40

    F3

    The square and round brackets denote closed and open intervals. A closed interval includes the number, and open interval excludes it. So 39.99999 gets grade F3, but 40 gets grade F2.

    Let xs = [83, 75, 74.9, 70, 69.9, 65, 60, 59.9, 55, 50, 49.9, 45, 44.9, 40, 39.9, 2, 0]

    Test your function by printing the mark and the grade for all the elements in this list.

  9. Modify the turtle bar chart program so that it leaves small gaps between each bar.

  10. Modify the turtle bar chart program so that the bar for any value of 200 or more is filled with red, values between [100 and 200) are filled yellow, and bars representing values less than 100 are filled green.

  11. In the turtle bar chart program, what do you expect to happen if one or more of the data values in the list is negative? Try it out. Change the program so that when it prints the text value for the negative bars, it puts the text below the bottom of the bar.

  12. Write a function find_hypot which, given the length of two sides of a right-angled triangle, returns the length of the hypotenuse. (Hint: x ** 0.5 will return the square root.)

  13. Write a function is_rightangled which, given the length of three sides of a triangle, will determine whether the triangle is right-angled. Assume that the third argument to the function is always the longest side. It will return True if the triangle is right-angled, or False otherwise.

    Hint

    floating point arithmetic is not always exactly accurate, so it is not safe to test floating point numbers for equality. If a good programmer wants to know whether x is equal or close enough to y, they would probably code it up as

    if  abs(x - y) < 0.000001:      # if x is approximately equal to y
    ...
  14. Extend the above program so that the sides can be given to the function in any order.