11. Bools and Boolean expressions

We have already seen the "bool" type in Python, which has two (and only two) members: True and False. Expressions producing these values are called Boolean expressions, and there are a few families of operators that produce these, which we discuss below. Some of these might be familiar from mathematics dealing with logic, comparison, Venn diagrams and piecewise functions. Our programming examples will include these, as well as expand them to even more applications, such as conditions (presented in the next section).

11.1. Bools and type conversion

We mentioned before that the bool type contains only 2 values: True and False. It is worth noting that Python considers 1, 1.0 and True to be equivalent; similarly, 0, 0.0 and False are equivalent. Though, note our previous caveats and reservations about the exactness of floats from evaluated expressions (here and here); that is, we might consider 0.0 exactly when it is directly typed that way, but not when it is the result of evaluated expression, due to roundoff error, etc.

What happens when we perform type conversion from other mathematical types to bools? How do all those many values that ints, floats, etc. can have map onto this much smaller set? For example, what do you expect is the result of each of these (after pondering, try each one on your system):

1bool(1)
2bool(0)
3bool(459.2)
4bool(-100)
5bool(100 - 10 * 10)
6bool(1e-15)

? Hopefully a pattern emerges:

  • The conversion of every nonzero value, whether positive or negative, and no matter how small (consider the last example in Line 6!), evaluates to True.

  • Only the conversion of an identically zero value evaluates to False. In Line 5, the expression inside the conversion should evaluate to an int value, so it should be exact.

In fact, we can generalize this, which will be useful to keep in mind as we learn more types. Every type has one null value: for int it is 0, for float it is 0.0, etc. For most types, inputting the null value, and only the null value, into bool() returns False, and inputting any other (= non-null) value returns True.

When converting from a bool to a different mathematical type, such as to int or float, we might not be surprised to see that True converts to unity and False to zero. Thus:

print(int(True))
print(float(False))

evaluates to:

1
0.0

and so forth.

Q: What is the null value of the str type, i.e., for what string value sss is bool(sss) False?

+ show/hide response

11.2. Logical operators

One family of Boolean expressions are the logical expressions, with associated things like logic tables, set comparison and Venn diagrams. Logical operators include and, or and not, whose names are self-descriptive. These operators evaluate to bool values, and they often (but not always) have bools as inputs.

Note that and and or are binary operators, taking one preceding and one following argument, such as in True and True. In contrast, not is a unary operator and takes a single following argument to the right, such as not True. Often, not almost looks like a function because we might wrap a whole expression in parentheses to negate that result, as in not(True and False).

These operators do not share equivalent precedence, to be just evaluated left to right, say. First, not will be evaluated, then and and then or. Consider the following examples:

True and True           # True
True and False          # False
False and False         # False
True or False           # True
True and False or True  # True
not False               # True
not(True or False)      # False
not(True or True)       # False
not True or True        # True

As usual, one must be careful with the order of operations---compare the difference in the result of the last two expressions above. In the final expression, the not operator acts before the or, so that not True evaluates first to False, and the resulting False or True evaluates to True. We tend to prefer parentheses with negation for visual clarity, even though there is only one operand: e.g., not(True) or True instead of the last example's format.

Q: What is the value of True or False and True?

+ show/hide response

Q: What is the value of True or True or True and False?

+ show/hide response

Q: What is the value of not 3?

+ show/hide response

11.2.1. Subtle point: and/or with non-bool operands

When we used and and or above, we were always using operands that were bools; the resulting evaluations were also bools, matching out mathematical syntax and expectation---great.

However, there is some subtley with using and and or in Python. Under the hood a more general calculation is performed which, if we have non-bool operands, we can observe. And it might be confusing at first, but let's roll with it. The rules are:

  • and returns the first operand whose value would produce False when put into bool(...); if none is found, it returns the last operand.

  • or returns the first operand whose value would produce True when put into bool(...); if none is found, it returns the first operand.

Indeed, those rules map onto normal mathematical logic when we have True and False inputs, but they also explain why the outputs of the following expressions are 100 and 4, respectively (and perhaps oddly):

4 and 100
4 or 100

To reduce potential/actual confusion, we should probably aim to just use True and False as operands as much as possible with and and or, possibly making our own explicit type conversion with bool(...). Sigh.

11.3. Membership operators

Another useful logical operator is the membership operator in which evaluates to True if it finds a specified element in a collection and False otherwise. There is also the two-word operator not in, which has the opposite output of in. We have one type so far that is a collection (a set of elements, which may be ordered or not), namely the str type, and we can see some examples with this:

'A' in 'A cold winter day'                                     # True
'A' not in 'A cold winter day'                                 # False
'winter' in 'A cold winter day'                                # True
'gs fo' in 'Scramble eggs for two please'                      # True
' ' in 'Scramble eggs for two please'                          # True
'Are creatures' in "Are alien creatures humans' illusion?"     # False

In each case, the operators care nothing about whether the strings have spaces or pieces of what we would consider words---the characters are all treated equally.

We will look at other collection types later, including mathematical ones, where this operator will have further use.

Q: What is the evaluation of 'C' in 'A cold winter day'?

+ show/hide response

Q: What is the evaluation of '' in 'A cold winter day'? Can you think of a replacement string for the one to the right of in, to produce an opposite bool?

+ show/hide response

11.4. Comparison (relational) operators

Another widely used type of Boolean expression is the family of comparison (or relational) expressions. These check the truth/falsehood of equalities and inequalities, greater than >, less than or equals <=, etc. It is in this context that we have the equality operator, which is written as == and returns a bool. And again, we emphatically distinguish the relational operator in A == B (the True/False statement, "A equals B") from the assignment operator in A = B (the declarative operation, "A is set to be B"), with the latter storing value on the computer for later reference with a name label.

There are several standard comparison operators, each taking two operands. Common ones are:

Comp symbol

Math Symbol

Description

==

=

equal

!=

\neq

not equal

>, <

>,~<

greater than, less than

>=, <=

\geq,~\leq

greater than or equals, less than or equals

Each operator compares what is on the LHS to what is on the RHS, such as in:

4 == 5
100 != 38
21 <= 55

which would, in order, evaluate to False, True and True.

Variables can be used on either side of the relational operator, as long as they have been defined previously, and so can other operations and arbitrary expressions. The following relational expressions are all allowed (and equivalent):

1val = 5                # assignment, *not* a relational op!
2val == 5
35 == val
45 - val == 0
53*0 == 3*(val - 5)

After the assignment operation in Line 1, the four other comparison operations (just algebraic variations of the same expression) would each produce the identical True result. Note the order of operations in the Line 4: the subtraction is performed before the equality; thus, we see the relational operators have lower operator precedence (introduced earlier) than the basic math operations. In more complicated expressions, parentheses may still be desired for clarity.

Note that the operands' type can also matter when performing comparisons. Consider the following examples (with results and notes shown in comments to the right of each):

 15 == 5.0                 # True
 23.2 % 1 == 0.2           # False
 3type(5) == int           # True
 4False == 0               # True
 5
 6val2 = 35.0              # assignment, not comparison!
 7val2 <= 50               # True
 8type(val2) != bool       # True
 9val2**0.5 - 5 < 1        # True
103 + ( val2 > 0 )         # 4
11(4 >= 3) + (9 != val2)   # 2

Some things to note from those examples:

  • Line 2: One must be careful when comparing equality of floating point numbers. The results of floating point calculations may be veeery close, say differing by < 10^{-15}, but they are still different. Some general problems with looking for equality from any floating point operation were introduced here, and the particular case of special considerations around 0.1 and 0.2 (because of their binary representations, or lack thereof) are detailed here.

  • Line 1: The types differ, but the value is exactly the same here. The floating point value is provided directly, not by evaluation of an expression (e.g., 25.0**0.5 or something more complicated), so this comparison may be reasonable.

  • Lines 3, 8: Checking the type of an object, and not just its value, can be quite useful. For example, you may want to restrict inputs to a function or part of code, to use only integer values.

  • Line 4: We mentioned above that in Python, True is equivalent to 1 and False to 0. Using the comparison operator like this, we can actually demonstrate this fact explicitly.

  • Lines 10, 11: Again, in more complicated expressions, the use of parentheses can be vital to clarify desired order of operations (even if only for human reading).

  • Line 11: The result of a Boolean expression can be used as a quantity itself. When using arithmetic operators, True is converted to 1, and False to 0.

The result of any of these expressions can also be saved for further use, via assignment:

xval = 3.2 % 1 == 1.2         # assigns False to xval
yval = -1 >= -10.5 < -4*20    # assigns False to yval
zval = xval == yval           # so this should ...?
print(zval)

As usual, the expression on the RHS of the = is evaluated first, and then that value is assigned to the variable name on the LHS.

The logical and comparison operators can be combined in expressions. Taking a look at these examples, which family of operators appears to have higher precedence?

not 3 == 0                 # True
not 3 != 5                 # False
10*7 == 70 and 12//3 == 4  # True

It appears that the comparison operators are evaluated before the logical ones. And since these are just expressions that get evaluated, their results can be saved in variables:

testval = not 3 == 5
print('The testvalue is =', testval)

11.4.1. Subtle point: Chaining comparisons

Python also allows for putting multiple comparisons together in the same expression (which is often not the case in other languages, such as C). This is referred to as chaining the comparison operators in the expression. Thus, the following are valid expressions:

5 < 25 < 100         # True
-1 >= -10.5 < -4*20  # False

x = math.log10(1024)
3 < x <= 4           # True

etc.

This kind of chained expression often comes up in mathematical discussions of ranges or piecewise functions; the open or closed boundaries can be expressed with <, > or <=, >=, respectively. For example, the mathematical statement of checking if a variable is in a given interval y\in(25, 50] would be expressed in Python as 25 < y <= 50. Note that this usage is only for asking if the chained expression is True or False: "is y in the interval...?"; y only has a single value here. The use of an interval where a variable has many values, such as, "let y be a variable in the range..." (which may occur in plotting, for example) is separate, and we will discuss that later.

One subtle point with chaining is that technically, Python converts chained expressions into separate comparisons for actual evaluation, so that A < B < C is actually evaluated as A < B and B < C, though B itself is only evaluated once (the logical operator and is discussed below). It can be important to remember what the exact interpretation by Python is, under the hood, so that unexpected results don't pop-up.

Q: What is the value of the chained comparison expression

True == False == False?

+ show/hide response

In the end, it might often be beneficial to not use comparison chaining with equality/inequality. It can easily lead to confusion and possible hard-to-spot bugs.

11.5. Final note: Operator precedence reference

We provide a link to the Python reference on operator precedence.

It contains many operations beyond the basic math and Boolean operations we have seen so far, but it is a useful reference.

11.6. Practice

First come up with what you think the answer should be, and then check the result using an ipython terminal:

  1. What are the differences in output of these expressions?

    False * True
    False and True
    

    Why is that the case?

  2. Let a = 56 and b = 12. Evaluate the following Boolean expressions:

    a % b == 8
    not (a+b == 68) or (a-b) % 3 == 1
    (-abs(b-a) > 0 or b-a > 0 ) or (a//b == 4 and not (a/b == 4))
    '8' in 'a+b'
    '8' in str(a+b)
    '61' in str(a)+str(b)
    a < b**2 < 70
    
  3. Evaluate the following Boolean expressions:

    0 or True
    bool(5) and True
    True and bool(5)
    'here' in 'There are people on the street'
    'the fake' in 'Given the former, the latter should be fake'
    
  4. Using your own choices for some (Boolean) values x, y and z, evaluate the following:

    not (x and not y) == (not x or y)
    (x and y) or z == (x or z) and (y or z)