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
Out[1]:
True
In [2]:
type(True)
Out[2]:
bool
In [3]:
False
Out[3]:
False
In [4]:
type(False)
Out[4]:
bool

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

In [5]:
true
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-724ba28f4a9a> in <module>
----> 1 true

NameError: name 'true' is not defined
In [6]:
false
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-6-b73d74fcede9> in <module>
----> 1 false

NameError: name 'false' is not defined

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.

In [7]:
3 < 5
Out[7]:
True
In [8]:
3 > 2
Out[8]:
True
In [9]:
5 == 5
Out[9]:
True
In [10]:
5 >= 5
Out[10]:
True
In [11]:
6 <= 5
Out[11]:
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).

In [12]:
'bat' < 'cat'
Out[12]:
True
In [13]:
'bat' < 'ant'
Out[13]:
False
In [14]:
'bat' == 'bat'
Out[14]:
True
In [15]:
'bat' < 'bath'
Out[15]:
True
In [16]:
'Cat' < 'bat'
Out[16]:
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 [17]:
print(ord('a'), ord('b'), ord('c'))
97 98 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 uppercase letters are internally represented with smaller numbers than lowercase letters. See the ASCII table and the examples below:

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

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

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 [20]:
not (3 > 5)
Out[20]:
True
In [21]:
not (3 == 3)
Out[21]:
False

and

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

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

or

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

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

Membership Operator in for Sequences

Let us practice some predicates and conditionals that involve sequences. A sequence is an ordered collection of items. For example, in Python a string is a sequence as it is an ordered collection of letters. A list in python is a another sequence and stores an ordered collection of items, enclosed within [].

We will study strings and lists in more detail in the coming lectures. Today we will use the in operator and not in operators to create Boolean expressions involving sequences.

in operator: s1 in s2 tests if string s1 is a substring of string s2
not in operator: returns the opposite of in, i.e., s1 not in s2 is the same as not s1 in s2

In [30]:
'134' in 'CS134'
Out[30]:
True
In [31]:
'era' not in 'generation'
Out[31]:
False
In [32]:
'grass' not in 'grassroots'
Out[32]:
False

Lists and the in operator: A list in python is just a collection of values enclosed in [].
item in myList tests if item is present in the list myList.

In [33]:
evenNums = [2, 4, 6, 8, 10]  # list of even numbers less than equal to 10
In [34]:
4 in evenNums
Out[34]:
True
In [35]:
5 in evenNums
Out[35]:
False
In [36]:
nameList = ['Anna', 'Chris', 'Zoya', 'Sherod', 'Zack']
In [37]:
'Shikha' in nameList
Out[37]:
False
In [38]:
'Chris' in nameList
Out[38]:
True

Predicates

Definition: A predicate is simply any function that returns a boolean value. Usually, the function body will contain a complex expression combining relational and logical expressions, as the following examples show.

In [39]:
def isHogwartsHouse(s):
    '''Given a string, returns True if it is a Hogwarts house'''
    return (s == 'Gryffindor' or s == 'Hufflepuff'
            or s == 'Ravenclaw' or s == 'Slytherin')  
    # notice the use of parenthesis to enclose multi-line expressions
    
In [40]:
isHogwartsHouse('Slytherin')
Out[40]:
True
In [41]:
isHogwartsHouse("Hagrid's hut")
Out[41]:
False

Expressing intervals of numbers: Below is a predicate that checks if a value is within a given interval of numbers.

In [42]:
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?

In [43]:
def isDivisibleBy(num, factor):
    return (num % factor) == 0 # notice the remainder operator 
In [44]:
isDivisibleBy(121, 11)
Out[44]:
True
In [45]:
isDivisibleBy(25, 3)
Out[45]:
False

Strings and Predicates

Let us practice some predicates and conditionals that involve strings. We will write a predicate isVowel that takes a character as input and returns true if it is a vowel. There are two ways to write such a predicate.

Helper function lower(): word.lower() returns a new string which is the string word in all lowercase letters.

In [46]:
# define the isVowel function

def isVowel1(char):
    '''Takes a char as input and determines if it is a vowel.  
    Function version without in'''
    c = char.lower()
    return c == 'a' or c == 'e' or c == 'i' or \
            c == 'e' or c == 'o' or c == 'u'
    # can also use \ for multi-line expressions

def isVowel2(char):
    '''Takes a char as input and determines if it is a vowel.
    Function version using in'''
    c = char.lower()
    return c in 'aeiou'
    
In [47]:
help(isVowel1)   # calling help on a function returns its docstring
Help on function isVowel1 in module __main__:

isVowel1(char)
    Takes a char as input and determines if it is a vowel.  
    Function version without in

In [48]:
print(isVowel1('e'), isVowel2('e'))
True True
In [49]:
print(isVowel1('b'), isVowel2('b'))
False False
In [50]:
print(isVowel1('U'), isVowel2('U'))
True True

Simple conditionals: If Statements

An if statement (also called a condtional statement) chooses between two branches based on a test value.

In [51]:
def abs(n):
    '''Return the absolute value of the number n'''
    if n >= 0:
        return n
    else:
        return -n
    
def classify(num):
    '''Return a string indicating whether num is negative or not.'''
    if num < 0:
        return 'negative'
    else:
        return 'nonnegative'
In [52]:
abs(-17)
Out[52]:
17
In [53]:
abs(111)
Out[53]:
111
In [54]:
classify(-17)
Out[54]:
'negative'
In [55]:
classify(111)
Out[55]:
'nonnegative'

A function with a conditional might print something.

In [56]:
def doWhenTemperature(temp):
    if temp <= 65:
        print("Put on a sweater or coat.")
    else:
        print("You can wear short sleeves today.")
In [57]:
doWhenTemperature(72)
You can wear short sleeves today.
In [58]:
doWhenTemperature(50)
Put on a sweater or coat.

Does doWhenTemperature return anything?

In [59]:
print(doWhenTemperature(50))
Put on a sweater or coat.
None

Function bodies and conditional branches with multiple statements

In [60]:
def categorize(num):
    '''This function has 3 statements in its body.
       They are executed from top to bottom, one after the other.
    '''
    print('Categorizing', num)
    if num % 2 == 0:
        print("It's even")
    else:
        print("It's odd")
    if num < 0: 
        '''This branch has 2 statements.'''
        print("It's negative")
        print("(That means it's less than zero)")
    else:
        print("It's nonnegative")
    
In [61]:
categorize(111)
Categorizing 111
It's odd
It's nonnegative
In [62]:
categorize(-20)
Categorizing -20
It's even
It's negative
(That means it's less than zero)

The pass statement and dropping else

When we don't want to do anything in a conditional branch, we use the special pass statement, which means "do nothing". (It's a syntax error to leave a branch blank.)

In [63]:
def warnWhenTooFast(speed):
    if speed > 55:
        print("Slow down! You're going too fast")
    else:
        pass # do nothing 
In [64]:
warnWhenTooFast(75)
Slow down! You're going too fast
In [65]:
warnWhenTooFast(40)

It's OK to have an if statement without an else clause. In this case, the missing else clause is treated as if it were a pass statement.

In [66]:
def warnWhenTooFast2(speed):
    if speed > 55:
        print("Slow down! You're going too fast")
In [67]:
warnWhenTooFast2(75)
Slow down! You're going too fast
In [68]:
warnWhenTooFast2(40)

Below are two correct variants of the abs absolute value function defined above. Explain why they work.

In [69]:
def abs2(n):
    '''returns the absolute value of n'''
    result = n
    if n < 0:
        result = -n 
    return result

print(abs2(-17), abs2(42))
17 42
In [70]:
def abs3(n):
    '''returns the absolute value of n'''
    if n < 0:
        return -n
    return n

print(abs3(-17), abs3(42))
17 42

Nested and chained conditionals

It often make sense to have a conditional statement nested inside the branch of another conditional.

Below we show variants of a function that returns the movie rating appropriate for a given age of movier goer. (If you want to learn more about film ratings, read this Wikipedia article.)

In [71]:
def movieAge1(age):
    if age < 8:
        return 'G'
    else:
        if age < 13:
            return 'PG'
        else: 
            if age < 18:
                return 'PG-13'
            else:
                return 'R'
In [72]:
movieAge1(5)
Out[72]:
'G'
In [73]:
movieAge1(10)
Out[73]:
'PG'
In [74]:
movieAge1(15)
Out[74]:
'PG-13'
In [75]:
movieAge1(20)
Out[75]:
'R'

Python uses chained (multibranch) conditionals with if, elifs, and else to execute exactly one of several branches.

In [76]:
def movieAge2(age):
    if age < 8:
        print('G')
    elif age < 13:
        print('PG')
    elif age < 18:
        print('PG-13')
    else:
        print('R')
In [77]:
movieAge2(5) 
G
In [78]:
movieAge2(10)
PG
In [79]:
movieAge2(15)
PG-13
In [80]:
movieAge2(20)
R

Remember: Only the first true branch will be executed.

Important: As shown in the following example, the order of chaining conditionals matters!

In [81]:
def movieAgeWrong(age):
    if age < 18:
        print('PG-13')
    elif age < 13:
        print('PG')
    elif age < 8:
        print('G')
    else:
        print('R')
In [82]:
movieAgeWrong(5)
PG-13
In [83]:
movieAgeWrong(10)
PG-13
In [84]:
movieAgeWrong(15)
PG-13
In [85]:
movieAgeWrong(20)
R

Exercise: daysInMonth

Define a function named daysInMonth that takes a month (as an integer) as the argument, and returns the number of days in it, assuming the year is not a leap year. Assume 1 is January, 2 is February, ..., 12 is December. If the month does not fall between 1 and 12, return an error message as a string. Make the function as concise as possible (group months by days, don't write 12 separate if-else clauses).

In [86]:
# Define your daysInMonth function below

def daysInMonth(month):
    '''Given a month between 1-12, returns the number of days in it,  
    assuming the year is not a leap year'''
    if month < 1 or month > 12:
        return 'Error: Month does not fall between 1-12'
    elif month == 2:
        return 28
    elif month == 4 or month == 6 == month == 9 or month == 11:
        return 30
    else:
        return 31
In [87]:
daysInMonth(4)  # April
Out[87]:
30
In [88]:
daysInMonth(8)  # August
Out[88]:
31
In [89]:
daysInMonth(2)  # February
Out[89]:
28
In [90]:
daysInMonth(13) # Error message
Out[90]:
'Error: Month does not fall between 1-12'

11. Improving the style of functions with conditionals

Having seen conditional statements, you may be tempted to use them in predicates. But most predicates can be defined without conditionals by using combinations of relational and logical operators. For example, compare the complicated and simplifed functions below:

In [91]:
def isFreezingComplex(temp):
    if temp <= 32:
        return True
    else:
        return False
    
def isFreezingSimple(temp):
    return temp <= 32
In [92]:
print(isFreezingComplex(20), isFreezingSimplified(20))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-92-91bad6f2d88d> in <module>
----> 1 print(isFreezingComplex(20), isFreezingSimplified(20))

NameError: name 'isFreezingSimplified' is not defined
In [97]:
print(isFreezingComplex(72), isFreezingSimple(72))
False False
In [98]:
def isPositiveEvenComplex(num):
    if num > 0:
        if num % 2 == 0:
            return True
        return False
    return False

def isPositiveEvenSimple(num):
    return num > 0 and num % 2 == 0
In [99]:
print(isPositiveEvenComplex(12), isPositiveEvenSimple(12))
True True
In [100]:
print(isPositiveEvenComplex(19), isPositiveEvenSimple(19))
False False
In [101]:
print(isPositiveEvenComplex(-3), isPositiveEvenSimple(-3))
False False