1. Boolean Values

Python has two values of the bool type, written True and False. These are called logical values
or Boolean values, named after 19th century mathematician George Boole.

In [1]:
True          # show the value
Out[1]:
True
In [2]:
type(True)    # check the type name
Out[2]:
bool
In [3]:
False         # show the value
Out[3]:
False
In [4]:
type(False)   # check the type name
Out[4]:
bool

Careful, the two values are written as uppercase, and you'll get an error if they are misspelled:

In [5]:
true
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 true

NameError: name 'true' is not defined
In [6]:
false
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 false

NameError: name 'false' is not defined

2. Relational Operators

We have seen arithmetic operators that produce as output numerical values. Today, we'll see relational operators that
produce as output Boolean values. Relational operators are used to compare two values.

Below try to first guess the values before running the cell.

In [7]:
3 < 5
Out[7]:
True
In [8]:
3 < 2
Out[8]:
False
In [9]:
3 > 2
Out[9]:
True
In [10]:
5 == 5
Out[10]:
True
In [11]:
5 >= 5
Out[11]:
True
In [12]:
6 <= 5
Out[12]:
False

Note: == is pronounced "equals" and != is pronounced "not equals". This is why we distinguish the pronunciation
of the single equal sign = as "gets", which is assignment and nothing to do with mathematical equality!

Relational operators can also be used to compare strings, in dictionary order. This means, the way words are ordered in a dictionary. Words that occur toward the start of a dictionary, are deemed "smaller" than words that occur toward the middle or end.

In [13]:
'bat' < 'cat'
Out[13]:
True
In [14]:
'bat' < 'ant'
Out[14]:
False
In [15]:
'bat' == 'bat'
Out[15]:
True
In [16]:
'bat' < 'bath'
Out[16]:
True
In [17]:
'Cat' < 'bat'
Out[17]:
True

EXPLANATION: How does this comparison of string values work? Python starts by comparing the first character of each string to one another. For example "b" with "c". Because the computer doesn't know anything about letters, it converts everything into numbers. Each character has a numerical code that is summarized in this table of ASCII codes. In Python, we can look up the ASCII code via the Python built-in function ord:

In [18]:
print("a", ord('a'))
print("b", ord('b'))
print("c", ord('c'))
a 97
b 98
c 99

As you can see, the value for 'b', 98, is smaller than the value for 'c', 99, thus, 'b' < 'c'. Once two unequal characters are found, Python stops comparing the other characters, because there is no point in continuing. However, if characters are the same, like in 'bat' and 'bath', the comparisons continue until the point in which something that differs is found. In this case, there is an extra 't', making 'bath' greater in value than 'bat'.

Uppercase vs. Lowercase: Counterintuitively, it turns out, the upppercase letters are internally represented with smaller numbers than lowercase letters. See the ASCII table and the examples below:

In [19]:
print("A", "is", ord('A'))
print("a", "is", ord('a'))
A is 65
a is 97
In [20]:
print("B", "is", ord('B'))
print("b", "is", ord('b'))
B is 66
b is 98

This explains why the world 'Cat' is smaller than the word 'cat'.

3. Logical Operators

There are three logical operators: not, and, or, which are applied on expressions that are already evaluated as boolean values.

not

not expression evaluates to the opposite of the truth value of expression

In [21]:
not (3 > 5) # parentheses not necessary - relational operators have higher precedence
Out[21]:
True
In [22]:
not (3 == 3)
Out[22]:
False

and

exp1 and exp2 evaluates to True iff both exp1 and exp2 evaluate to True.

In [23]:
True and True
Out[23]:
True
In [24]:
True and False
Out[24]:
False
In [25]:
(3 < 5) and ('bat' < 'ant')
Out[25]:
False
In [26]:
(3 < 5) and ('bat' < 'cat')
Out[26]:
True

or

exp1 or exp2 evaluates to True iff at least one of exp1 and exp2 evaluate to True.

In [27]:
True or True
Out[27]:
True
In [28]:
True or False
Out[28]:
True
In [29]:
(3 > 5) or ('bat' < 'cat')
Out[29]:
True
In [30]:
(3 > 5) or ('bat' < 'ant')
Out[30]:
False

4. Combining logical operators

You can assign booleans to variables just like any other value. The variables below represent whether someone likes particular genres of music.

Note: Try to guess each expression before running the code.

In [31]:
# change these as you like to experiment
pop = False
rap = True
hiphop = True
In [32]:
not pop
Out[32]:
True
In [33]:
pop and rap
Out[33]:
False
In [34]:
rap and hiphop
Out[34]:
True
In [35]:
pop or hiphop
Out[35]:
True
In [36]:
hiphop or rap
Out[36]:
True

What are the order of operations here? Do we compute the operator and first or or?

In [37]:
rap or hiphop and pop
Out[37]:
True

and takes precendence over or, so the above expression is the same as

In [38]:
rap or (hiphop and pop)
Out[38]:
True

but not the same as

In [39]:
(rap or hiphop) and pop
Out[39]:
False

Note: While rap or hiphop and pop and rap or (hiphop and pop) in the examples above are equivalent, that doesn't nesserarily mean that Pythohn evaluates first the and operation. If rap is True, the rest of the expression is never executed, because it doesn't matter. When one operand is True, the result of an OR operation is always TRUE. Only if the operand is False, will Python continue evaluating the second operand of the OR expression.

5. Predicates

Definition: A predicate is a function that returns a boolean value.

In [40]:
def doILikeMissyElliott(rap, hiphop, pop):
    """determine if I like Missy Elliott."""
    return rap or hiphop and pop
In [41]:
doILikeMissyElliott(False, True, True)
Out[41]:
True
In [42]:
doILikeMissyElliott(True, True, False)
Out[42]:
True
In [43]:
doILikeMissyElliott(False, False, True)
Out[43]:
False

Usually, the function body will contain a complex expression combining relational and logical expressions, as the following examples show:

In [44]:
def isFaculty(name):
    """determine if the name belong to a CS 111 faculty member."""
    return (name == 'Carolyn' or name == 'Sohie'
            or name == 'Peter' or name == 'Lyn')
In [45]:
isFaculty('Carolyn')
Out[45]:
True
In [46]:
isFaculty("peter")
Out[46]:
False

Note: Explain the result of the last cell. Is that what you expected?

Expressing intervals of numbers: We can combine relational expressions to create intervals of numbers that fulfill certain criteria. Below is a predicate that checks if a value is within a given interval of numbers.

In [47]:
def isBetween(n, lo, hi):
    """determines if n is between lo and hi"""
    return (lo <= n) and (n <= hi)

More fun with Math: Is a number divisible by a factor? Is it even?

In [48]:
def isDivisibleBy(num, factor):
    """determines if num is divisible by factor"""
    return (num % factor) == 0 # notice the remainder operator 

def isEven(n):
    """determines if n is even"""
    return isDivisibleBy(n, 2)

print('Is 3774 divisible by 11?', isDivisibleBy(3774, 11))
print('Is 473 even?', isEven(473))
Is 3774 divisible by 11? False
Is 473 even? False

Is n a prime integer less than 100? In the solution below, notice how we split the long expression across mutliple lines for readability, and wrap all multiline expressions in parentheses.

In [49]:
# Version with continuation characters for multiline expressions
def isSmallPrime(n):
    return (isinstance(n, int)  # is n an integer?
            and (n > 1) and (n < 100)  # is n between 1 and 100?
            and (n==2 or n==3 or n==5 or n==7  # is n 2, 3, 5, or 7?
                 or not (isDivisibleBy(n,2)    #is n divisible by 2, 3, 5, or 7?
                         or isDivisibleBy(n,3)
                         or isDivisibleBy(n,5)
                         or isDivisibleBy(n,7))))
In [50]:
isSmallPrime(23)
Out[50]:
True
In [51]:
isSmallPrime(42)
Out[51]:
False

When multiline expressions are not wrapped in parentheses, you must use the special continuation character \ at the end of each line. No character other than a newline can come after the \.

In [52]:
# Version with continuation characters for some multiline expressions
def isSmallPrime(n):
    return isinstance(n, int) \
           and (n > 1) and (n < 100) \
           and (n==2 or n==3 or n==5 or n==7    # No need for \ because of open parentheses
                or not (isDivisibleBy(n,2)
                        or isDivisibleBy(n,3)
                        or isDivisibleBy(n,5)
                        or isDivisibleBy(n,7)))

The version below is an alternative solutions that uses De Morgan's laws.

In [53]:
# alternate version using De Morgan's laws
def isSmallPrime2(n):
    return (isinstance(n, int)
            and (n > 1) and (n < 100)  # is n between 1 and 100?
            and (n==2 or n==3 or n==5 or n==7  # is n 2, 3, 5, or 7?
                 or (not isDivisibleBy(n,2)   #is n divisible by 2, 3, 5, or 7?
                     and not isDivisibleBy(n,3)
                     and not isDivisibleBy(n,5)
                     and not isDivisibleBy(n,7))))

6. Testing for substrings with in and not in

Python's infix s1 in s2 operator tests if string s1 is a substring of string s2. For example:

In [54]:
'put' in 'computer'
Out[54]:
True
In [55]:
'opt' in 'computer'
Out[55]:
False

Although the letters 'o', 'p', and 't' all occur in 'computer' in left-to-right order, 'opt' itself is not a string that appears in 'computer'.

In [56]:
'era' in 'generation'
Out[56]:
True

Below, use in to show four other English words that are substrings of 'generation'

In [57]:
# Your code here
'gene' in 'generation'
Out[57]:
True
In [58]:
# Your code here
'rat' in 'generation'
Out[58]:
True
In [59]:
# Your code here
'ratio' in 'generation'
Out[59]:
True
In [60]:
# Your code here
'ion' in 'generation'
Out[60]:
True

s1 not in s2 means the same thing as not s1 in s2:

In [61]:
'get' not in 'generation'
Out[61]:
True
In [62]:
'era' not in 'generation'
Out[62]:
False

7. Exercise: Define these predicates

Write the following four predicates.
Remember: a predicate is simply a function that returns a Boolean value.

isFreezing: is the temperature in Fahrenheit at or below freezing?
isLongName: is a name longer than 20 characters?
isVowel: is a character a vowel?
startsWithVowel: does a string start with a vowel?

Do not use conditional statements to solve these problems. Only relational or logical expressions!

7.1 isFreezing

In [63]:
# Your code here
def isFreezing(temp):
    return temp <= 32
In [64]:
isFreezing(10)
Out[64]:
True
In [65]:
isFreezing(75)
Out[65]:
False

7.2 isLongName

In [66]:
# Your code here
def isLongName(name):
    return len(name) > 20
In [67]:
isLongName('Wellesley')
Out[67]:
False
In [68]:
isLongName('Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch')
Out[68]:
True

(Above is the name of a village in Wales; you can hear it pronounced in this video.)

7.3 isVowel

Recall that s.lower() returns a copy of the string s in which all leters are lowercase

In [69]:
'ABRACADABRA'.lower()
Out[69]:
'abracadabra'
In [70]:
'bUnNy'.lower()
Out[70]:
'bunny'
In [71]:
# There are multiple possible solutions for this problem. 
# First develop a solution that *does* use .lower() but does *not* use `in`
# Your code here
def isVowel(letter):
    letterLower = letter.lower()
    return (letterLower == 'a' or 
            letterLower == 'e' or 
            letterLower == 'i' or 
            letterLower == 'o' or
            letterLower == 'u')
In [72]:
isVowel('e')
Out[72]:
True
In [73]:
isVowel('U')
Out[73]:
True
In [74]:
isVowel('b')
Out[74]:
False

An alternative solution

There are many ways to define the function isVowel. Below, define a version of isVowel that uses both .lower() and in. Make sure it returns false for the input IOU!

In [75]:
# Your code here
def isVowel(letter):
    return len(letter) == 1 and letter.lower() in 'aeiou'
In [76]:
isVowel('a')
Out[76]:
True
In [77]:
isVowel('IOU')
Out[77]:
False

Note that the isVowel('IOU') might return True if isVowel doesn't check that the input is a string with only one letter!

7.4 startsWithVowel

Note: if s is a string, s[0] returns the first character of s.

In [78]:
s = "Boston"
s[0]
Out[78]:
'B'
In [79]:
# Your code here
def startsWithVowel(word):
    return isVowel(word[0])
In [80]:
startsWithVowel('Esmeralda')
Out[80]:
True
In [81]:
startsWithVowel('bravery')
Out[81]:
False

8. Digging Deeper: Quirks with Relational Operators

What happens if we compare numerical to string values?

In Python 3, numerical and string values are never equal. Furthermore, attempting to use >, >=, <=, or < will result in a TypeError.

Try to guess the outputs of these relational expressions before running the cells.

In [82]:
7 == '7'
Out[82]:
False
In [83]:
10 < '10'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[83], line 1
----> 1 10 < '10'

TypeError: '<' not supported between instances of 'int' and 'str'
In [84]:
10000000 >= '0'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[84], line 1
----> 1 10000000 >= '0'

TypeError: '>=' not supported between instances of 'int' and 'str'

What happens if we compare numerical to boolean values?

In Python 3, in comparisons involving numbers and booleans, False is considered a synonym for 0 and True is considered a synonym for 1.

In [85]:
0 == False
Out[85]:
True
In [86]:
1 == True
Out[86]:
True
In [87]:
2 > True
Out[87]:
True
In [88]:
1 > True
Out[88]:
False
In [89]:
1 > False
Out[89]:
True
In [90]:
0 > False
Out[90]:
False

What to take away from the above examples: Python 3 allows the comparison of values of different types in some contexts. In general, it's better to stick with comparing values of the same type.

9. Digging Deeper: Quirks with Logical Operators; Truthy and Falsey

In Python, not has surprising behavior when given a non-boolean operand:

In [91]:
not 111
Out[91]:
False
In [92]:
not 0
Out[92]:
True
In [93]:
not 'ab'
Out[93]:
False
In [94]:
not ''
Out[94]:
True

Truthy vs. Falsey Values:

What's going on in the above examples?

In Python, it turns out that in many contexts where a boolean is normally expected, 0 and 0.0 are treated like False and all other numbers are treated like True. So not 0 evaluates to True and not 111 evaluates to False.

Similarly, the empty string is treated like False and nonempty strings are treated like True. So not '' evaluates to True and not 'ab' evaluates to False.

In contexts where a boolean is normally expected, values that act like True are called Truthy and values that act like False are called Falsey. So 0, 0.0, '' and False are Falsey values, while all other numbers, strings, and booleans are Truthy.

Sadly, things are more complicated when it comes to testing equality:

In [95]:
0 == False
Out[95]:
True
In [96]:
'' == False # Only 0 and 0.0 are considered equal to False
Out[96]:
False
In [97]:
1 == True
Out[97]:
True
In [98]:
17 == True # Only 1 and 1.0 are considered equal to True
Out[98]:
False
In [99]:
'abc' == True
Out[99]:
False

and and or also behave in surprising ways when their operands are not booleans:

In [100]:
111 or 230 # If first value is Truthy, return it; otherwise return second
Out[100]:
111
In [101]:
0 or 230
Out[101]:
230
In [102]:
0 or 0.0
Out[102]:
0.0
In [103]:
'cat' or 'dog'
Out[103]:
'cat'
In [104]:
'' or 'dog'
Out[104]:
'dog'
In [105]:
0 or ''
Out[105]:
''
In [106]:
111 and 230 # If first value is Falsey, return it; otherwise return second
Out[106]:
230
In [107]:
0 and 230
Out[107]:
0
In [108]:
0 and 0.0
Out[108]:
0
In [109]:
'cat' and 'dog'
Out[109]:
'dog'
In [110]:
'' and 'dog'
Out[110]:
''
In [111]:
0 and ''
Out[111]:
0

The following definition of isVowel doesn't work. Explain why!

In [112]:
def isVowelWrong(s):
    low = s.lower()
    return low == ('a' or 'e' or 'i' or 'o' or 'u')
In [113]:
isVowelWrong('a') # This works
Out[113]:
True
In [114]:
isVowelWrong('b') # This works
Out[114]:
False
In [115]:
isVowelWrong('e') # This doesn't work. Why?
Out[115]:
False

Solution notes go here:

Because 'a' is Truthy, 'a' or 'e' evaluates to 'a'. Similarly, ('a' or 'e' or 'i' or 'o' or 'u') is equivalent to 'a', and return low == ('a' or 'e' or 'i' or 'o' or 'u') is equivalent to return low == 'a'!

Short-circuit Evaluation with and and or

Not only does or return the value of its left operand if it is Truthy; in this case the right operand is not even evaluated! This is called short-circuit evaluation of or:

In [116]:
(2 < 3) or ((1/0) > 0) # No ZeroDivisionError occurs because left operand is Truthy and right operad is never evaluated
Out[116]:
True
In [117]:
42 or ((1/0) > 0)
Out[117]:
42
In [118]:
((1/0) > 0) or (2 < 3) # ZeroDivisionError occurs because (1/0) is evaluated in left operand
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[118], line 1
----> 1 ((1/0) > 0) or (2 < 3) # ZeroDivisionError occurs because (1/0) is evaluated in left operand

ZeroDivisionError: division by zero
In [119]:
(2 > 3) or ((1/0) > 0) # ZeroDivisionError occurs because left operand is False and (1/0) is evaluated in right operand
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[119], line 1
----> 1 (2 > 3) or ((1/0) > 0) # ZeroDivisionError occurs because left operand is False and (1/0) is evaluated in right operand

ZeroDivisionError: division by zero

Similarly, if the left operand of and is Falsey, it is returned immediately without even evaluating the right operand.

In [120]:
(2 > 3) and ((1/0) > 0)
Out[120]:
False
In [121]:
0 and ((1/0) > 0)
Out[121]:
0
In [122]:
'' and ((1/0) > 0)
Out[122]:
''
In [123]:
(2 < 3) and ((1/0) > 0)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[123], line 1
----> 1 (2 < 3) and ((1/0) > 0)

ZeroDivisionError: division by zero
In [ ]: