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?
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
?
Q: What is the value of True or True or True and False
?
Q: What is the value of not 3
?
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 intobool(...)
; if none is found, it returns the last operand.or
returns the first operand whose value would produce True when put intobool(...)
; 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'
?
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?
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
!=
not equal
>
,<
greater than, less than
>=
,<=
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 , 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 to1
andFalse
to0
. 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, andFalse
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 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
?
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:
What are the differences in output of these expressions?
False * True False and True
Why is that the case?
Let
a = 56
andb = 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
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'
Using your own choices for some (Boolean) values
x
,y
andz
, evaluate the following:not (x and not y) == (not x or y) (x and y) or z == (x or z) and (y or z)