Programs get really interesting when we can test conditions and change the program behavior depending on the outcome of the tests. That’s what this chapter is about.
A Boolean value is either true or false. It is named after the British mathematician, George Boole, who first formulated Boolean algebra — some rules for reasoning about and combining these values. This is the basis of all modern computer logic.
In Python, the two Boolean values are True and False (the capitalization must be exactly as shown), and the Python type is bool.
>>> type(True) <class 'bool'> >>> type(true) Traceback (most recent call last): File "<pyshell#3>", line 1, in <module> type(true) NameError: name 'true' is not defined
A Boolean expression is an expression that evaluates to produce a result which is a Boolean value. For example, the operator == tests if two values are equal. It produces (or yields) a Boolean value:
>>> 5 == (3 + 2) # Is five equal 5 to the result of 3 + 2? True >>> 5 == 6 False >>> j = "hel" >>> j + "lo" == "hello" True
In the first statement, the two operands evaluate to equal values, 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 which all produce a bool result; here are all six:
x == y # Produce True if ... x is equal to y 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, 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 =>.
Like any other types we’ve seen so far, Boolean values can be assigned to variables, printed, etc.
There are three logical operators, and, or, and not, that allow us to build more complex Boolean expressions from simpler Boolean expressions. The semantics (meaning) of these operators is similar to their meaning in English. For example, x > 0 and x < 10 produces 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 n is divisible by 2 or it is divisible by 3. What do you think happens if n is divisible by both 2 and by 3 at the same time? Will the expression yield True or False? Let’s try it:
Finally, the not operator negates a Boolean value, so not (x > y) is True if (x > y) is False, that is, if x is less than or equal to y.
The expression on the left of the or operator is evaluated first: if the result is True, Python does not (and need not) evaluate the expression on the right — this is called short-circuit evaluation. Similarly, for the and operator, if the expression on the left yields False, Python does not evaluate the expression on the right. In this way, there are no unnecessary evaluations.
A truth table is a small table that allows us to list all the possible inputs, and to give the results for the logical operators. Because the and and or operators each have two operands, there are only four rows in a truth table that describes the semantics of and.
a b a and b False False False False True False True False False True True True
In a Truth Table, we sometimes use T and F as shorthand for the two Boolean values: here is the truth table describing or:
a b a or b F F F F T T T F T T T T
The third logical operator, not, only takes a single operand, so its truth table only has two rows:
a not a F T T F
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:
Try changing the value of x in the above program, and see how the output changes.
The Boolean expression after the if statement is called the condition. If it is true, then all the indented statements get executed. If not, then all the statements indented under the else clause get executed.
Flowchart of an if statement with an else clause
The syntax for an if statement looks like this:
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 indented 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 we haven’t written yet). In that case, we can use the pass statement, which does nothing except act as a placeholder.
Flowchart of an if statement with no 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.
In this case, the print function that outputs the square root is the one after the if block. As the code is originally written, the condition of the if statement is False, so the block of code inside the if block gets skipped. Change the value of x to something negative and run it again to see the difference.
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.
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:
Flowchart of this chained conditional
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:
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.
One conditional can also be nested within another. (It is the same theme of composability, again!) We could have written the previous example as follows:
Flowchart of this nested conditional
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 we can.
Logical operators often provide a way to simplify nested conditional statements. For example, we can rewrite the following code using a single conditional:
The print function is called only if we make it past both the conditionals, so instead of the above which uses two if statements each with a simple condition, we could make a more complex condition using the and operator. Now we only need a single if statement:
The return statement, with or without a value, depending on whether the function is fruitful or void, allows us to terminate the execution of a function before (or when) we reach the end. One reason to use an early return is if we detect an error condition:
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.
Each of the six relational operators has a logical opposite: for example, suppose we can get a driver’s licence when our age is greater or equal to 16, we can not get the driver’s licence when we are less than 16.
Notice that the opposite of >= is <.
operator logical opposite == != != == < >= <= > > <= >= <
Understanding these logical opposites allows us to sometimes get rid of not operators. not operators are often quite difficult to read in computer code, and our intentions will usually be clearer if we can eliminate them.
For example, if we wrote this Python:
it would probably be clearer to use the simplification laws, and to write instead:
Two powerful simplification laws (called de Morgan’s laws) that are often helpful when dealing with complicated Boolean expressions are:
not (x and y) == (not x) or (not y) not (x or y) == (not x) and (not y)
For example, suppose we can slay the dragon only if our magic lightsabre sword is charged to 90% or higher, and we have 100 or more energy units in our protective shield. We find this fragment of Python code in the game:
de Morgan’s laws together with the logical opposites would let us rework the condition in a (perhaps) easier to understand way like this:
We could also get rid of the not by swapping around the then and else parts of the conditional. So here is a third version, also equivalent:
This version is probably the best of the three, because it very closely matches the initial English statement. Clarity of our code (for other humans), and making it easy to see that the code does what was expected should always be a high priority.
As our programming skills develop we’ll find we have more than one way to solve any problem. So good programs are designed. We make choices that favour clarity, simplicity, and elegance. The job title software architect says a lot about what we do — we are architects who engineer our products to balance beauty, functionality, simplicity and clarity in our creations.
Tip
Once our program works, we should play around a bit trying to polish it up. Write good comments. Think about whether the code would be clearer with different variable names. Could we have done it more elegantly? Should we rather use a function? Can we simplify the conditionals?
We think of our code as our creation, our work of art! We make it great.
We’ve had a first look at this in an earlier chapter. Seeing it again won’t hurt!
Many Python types come with a built-in function that attempts to convert values of another type into its own type. The int function, for example, takes any value and converts it to an integer, if possible, or complains otherwise:
>>> int("32") 32 >>> int("Hello") Traceback (most recent call last): File "<pyshell#6>", line 1, in <module> int("hello") ValueError: invalid literal for int() with base 10: 'hello'
int can also convert floating-point values to integers, but remember that it truncates the fractional part:
>>> int(-2.3) -2 >>> int(3.99999) 3 >>> int("42") 42 >>> int(1.0) 1
The float function converts integers and strings to floating-point numbers:
>>> float(32) 32.0 >>> float("3.14159") 3.14159 >>> float(1) 1.0
It may seem odd that Python distinguishes the integer value 1 from the floating-point value 1.0. They may represent the same number, but they belong to different types. The reason is that they are represented differently inside the computer.
The str function converts any argument given to it to type string:
>>> str(32) '32' >>> str(3.14149) '3.14149' >>> str(True) 'True' >>> str(true) Traceback (most recent call last): File "<pyshell#7>", line 1, in <module> str(true) NameError: name 'true' is not defined
str will work with any value and convert it into a string. As mentioned earlier, True is Boolean value; true is just an ordinary variable name, and is not defined here, so we get an error.
Functions can return Boolean values, which is often convenient for hiding complicated tests inside functions. For example:
It is common to give Boolean functions names that sound like yes/no questions. is_divisible returns either True or False to indicate whether the x is or is not divisible by y.
We can make the function more concise by taking advantage of the fact that the condition of the if statement is itself a Boolean expression. We can return it directly, avoiding the if statement altogether:
Boolean functions are often used in conditional statements:
It might be tempting to write something like:
but the extra comparison is unnecessary.