Chapter 9 - Boolean operators, and more about the return statement

This section introduces the boolean operators represented by the Python keywords and, or, and not. Boolean operators enable us to simplify the logic of conditional statements by combining boolean expressions.

1.1 The boolean operators

Let’s start by looking at an example that we used in a previous chapter: Mousetraps are sold online for 2.00 each plus .50 for shipping, with free shipping for orders of 30 or more. The logic that we used to calculate the shipping could be expressed like this (where num refers to the number ordered):

if num >= 30:
    shipping_cost = 0
else:
    shipping_cost = num * .50

Now suppose that shipping is also free for customers in zip code 50011. So before computing the shipping cost, we have to check the zip code. Assume that zip_code is a variable representing the zip code.

if num >= 30:
    shipping_cost = 0
else:
    if zip_code == 50010:
        shipping_cost = 0;
    else:
        shipping_cost = num * .50

This makes sense, but doesn’t it seem a bit strange that the line ``shipping_cost = 0``appears in two places?

After all, there are only two possibilities, either the shipping cost is zero, or else it’s num times 50 cents. It seems like what we really want to say is something like this:

_static/shipping1.PNG

So what goes in the blank? Logically we want to say “if num is at least 30 or the zip code is 50011, then the shipping is free”. Well, that’s the purpose of the or operator in Python. We can just write:

if num >= 30 or zip_code == 50010:
    shipping_cost = 0
else:
    shipping_cost = num * .50

To define exactly what the or operator does, let’s first summarize the possibilities and list the outcomes we intend. Each of the expressions can be either true or false, so there are four possibilities.

If num is at least 30 and the zip code is 50011, then shipping is free.

If num is at least 30 and the zip code isn’t 50011, then shipping is free.

If num is less than 30 but the zip code is 50011, then shipping is free.

Finally if num is less than 30 and the zip code isnt 50011, then shipping is not free.

_static/boolean_1.PNG

This table tells us under what conditions this new expression should be true.

In fact, we can use the same table to define the or operator for any two boolean expressions A and B.

_static/ab.PNG

The table tells us that the new expression A or B is true if at least one or A or B is true. This is called the truth table for the or operator.

Let’s try another example. Suppose we have a function print_grade that prints the grade for a given score, but we first have to check that the score is between 0 and 100.

We could write something like this:

if score >= 0:
    if score<100
        print_grade(score)
    else:
        print("out of range")
else:
    print("out of range")

But again, it seems odd to have the same print statement appearing in two places. There are really only two possible outcomes, so we should be able to write something like this:

_static/and_example.PNG

Now, what goes in the blank? We are trying to say “If score is above zero and less than 100, then print the grade.” That’s the purpose of the and keyword in Python:

if score >= 0 and score <= 100:
    print_grade(score)
else:
    print("out of range")

The new expression is true just when both of the original expressions are true. The and operator is defined with the following truth table.

_static/and_table.PNG

The way that the and keyword is used corresponds to the way we use the word “and” in English. If someone says “My date was rich and handsome,” under what conditions is this a true statement? The guy might be rich, the guy might be handsome, but the statement “My date was rich and handsome” is only true when he’s both. Poor and handsome, or rich and ugly, just won’t cut it.

The way that the or keyword works is similar to the word “or” in English, but only when it is used in the “inclusive” sense. If you ask someone, “Are you crazy or just plain stupid?” and he says yes, it means he might be crazy, he might be stupid, but he might be both, and his answer “yes” would be true in all three cases. That’s the inclusive “or”. On the other hand, when the waiter asks you, “Would you like the soup or the salad?” he usually means you can choose a soup, OR a salad, but not both. That’s the exclusive “or”. The or keyword works like the inclusive “or” in English.

There is one more boolean operator, called not. Unlike the and and or operators, the not operator doesn’t combine two expressions. It applies to just one expression. Just like you can take a number put a minus sign in front of it, and get the negative of the number, you put not in front of any boolean expression to get the opposite value.

For example, the expression

not(x > 0)

has the same meaning as

x <= 0

Likewise, the expression

not(x == 100)

means the same thing as

x != 100

What about something like this?

not(score >= 0 and score <= 100)

You can read this as, “it is not the case that score is between 0 and 100”, which is the same as saying the score is less than 0 or is greater than 100:

score < 0 or score > 100

In all these examples, it was easy to rewrite the expression so that it doesn’t use the not operator. In some cases it is not so simple. Here is an example in which the not operator is extremely useful. We previously wrote a function is_divisible(a, b) that returns True when a is divisible by b and False otherwise. What if, given two values x and y, we want to say, “x is not divisible by y?” Well, one idea is to write a new function, but that is not always practical. But it is easy to simply write the expression

not is_divisible(x, y)

which has value True exactly when is_divisible(x, y) has value False.

The operators or, and, and not can be repeated or combined. For example, you could say “x is equal to 3, 5, or 7” using the expression

x == 3 or x == 5 or x == 7

If you wanted to write a condition that says

“either x is equal to y, or else y is strictly between x and x squared”,

it would look like this:

x == y or (x < y and x * x > y)

Technically we don’t need the parentheses above, but it is usually more clear just to include them rather than scratching your head over whether they are necessary. The rules for precedence are analogous to the rules for addition and multiplication, where or is like addition and and is like multiplication. The not operator has higher precedence that and and or, so when we write

not(score >= 0 and score <= 100)

the parentheses are definitely necessary.

1.2 More about the return statement and flow of control

There is something you have surely noticed about calling functions. Take another look at our first function, the function that prints a verse of the song “99 bottles.”

Think about the way the function call is altering the flow of control. When sing_verse is called, the interpreter goes to the sing_verse function and executes the statements it finds there. When the function ends, control returns to the exact point where the function was called, and execution continues from that point.

_static/function_calls.PNG

We have used return statements in our value-returning functions in order to specify the value of the function. It turns out that a return statement has another purpose too: to actually cause the flow of control to literally return to the point where the function was called. For a function that doesn’t return a value, this happens automatically at the end of the function, as in sing_verse above. In the value-returning functions we have written so far, we have used a single return statement at the end of the function body, which is usually a good approach.

It is also possible to put return statements in the middle of the function body, and you should know how this works. Whenever the interpreter encounters a return statement, it stops executing the statements in the function body right then and there, and returns control to the point at which the function was called.

As an example let’s look again at the letter_grade function we wrote previously.

_static/scores.PNG

Notice that there are four alternative actions (assigning a letter to the grade variable) and we’re using the elif so that exactly one of them will execute. Here is another way the function could be written.

How does this work? Let’s see what happens if the score is 85. Is 85 above 90? No, so the if-block, that is, the line return "A", is skipped. Is 85 above 80? Yes, so we execute the if-block, which contains the line return "B". The return statement does two things here: it gives the function the value “B”, and it causes the interpreter to immediately return to where the function was called. That is, the rest of the statements are not executed at all. Try this with codelens.

_static/score_executable.PNG

For non-value-returning functions, it’s possible to use a return statement all by itself, that is, without an expression following it. This causes the interpreter to immediately return to where the function was called, but without returning a value. That is, the statement

return

is exactly the same as

return None

If a function doesn’t return a value, you normally don’t need a return statement. Returning from a function happens automatically whenever the interpreter reaches the last statement in the function. When you write a function with multiple returns, you have to be careful to make sure there’s a return statement along every possible path. For example consider this simple function for determining the maximum of two numbers, which contains a subtle error.

Try out the output. None? What happened? If first and second are the same, then neither one of the return statements will be executed. When control reaches the end of the function, it returns automatically with the value None.

Programming with multiple return statements is convenient sometimes and we will do it occasionally, especially when we start programming with loops. But for now you will probably agree that our first version of letter_grade, using elif was much easier to understand than the version above with multiple return statements. Unless there is a good reason to do otherwise, you should use a single return statement at the end of the function body.