3.1 If, Elif, Else Clauses¶
In this note, I will discuss conditional statements, and how we can use them to control the execution of our program under specific conditions.
Booleans¶
Now it is time to introduce another variable type in programming. Just like int, str, and NoneType, we now have another type to analyze: bool (short for boolean). A bool is something that is true or false. There are really no other values that a bool can take on. However, other types can thought of as boolean-like types, for which we call them "truthy" or "falsey" values.
Truthy and Falsey Values¶
We assign truth values to each of the types we know so far, soon we will grow these items to include more types of objects. For now, I would remember these items:
-
For an
inttype, every single number except0is truthy. So,0is false, and-10is true. -
For a
strtype, only the empty string (written as"") is falsey, and every non-empty string is truthy. That means a string like" "is truthy since is it non-empty (there is a space between the quotes). -
Noneis falsey -
We can say the truth value explicitly with
TrueandFalse. Of course,Trueis truthy andFalseif falsey. -
We will encounter other types of objects, however, a general rule is that an item is falsey if it is the empty for of that object, otherwise it is truthy.
You can actually see if items are truthy and falsey by calling bool over the item you seek the truth of. For example, in your Python interpreter, you can write:
>>> bool(4)
True
>>> bool(None)
False
>>> bool("")
False
>>> bool(" ")
True
>>> bool(0)
False
>>> bool(True)
True
Truthy values pop-up every once in a while, and these truthy values are specific to the Python language.
When we look at the Scheme language, truthy values are slightly different. (In the language C, truthy values are also different, and the same is apparent in Java, Rust, Go, etc.). You'll have to briefly look at the coding language you use to see what is truthy and what is falsey. However, the above is what Python considers true and false.
Logical Operators and Short Circuiting¶
Sometimes, we rely on multiple conditions being truthy, and other times, we rely on only one of many possible conditions being truthy. Thus, logical operators are used to be the solution to this problem.
Python's logical operators are the English words and, or, and not. (In other languages, you might see it as &&, ||, and !, respectively). Logical statements in Python therefore reads like English.
Logically, A and B requires both conditions A and B to be true. We can chain them together such as A and B and C and D and E to require A, B, C, D, and E to be true. If any of them are falsey values, the whole logical statement is falsey.
A or B requires only one of the items to be truthy. A could be truthy and B could be falsey, it can appear vice versa, or A and B are truthy. Only when both entries are falsey does this logical operator become falsey. Like chaining ands together, we can chain or. A or B or C or D or E requires at least one of the 5 conditions to be truthy to be a truthy statement (there could be two, three, four, five truthy values, but as long as one of them are truthy, it doesn't really matter what the others are). It will be falsey only if all 5 conditions are falsey.
not simply switches the truthy value of the item it is applied to. not True is falsey, and not False is truthy.
However, there is something unique about logical operators. They actually return something (like a function) besides True and False.
Let's investigate this behavior. Look at these two statements:
>>> 0 or 5
5
>>> 0 and 5
0
It seems the or evaluated to the second item, and the and evaluated the first item. Let's also look at
>>> 5 or 0
5
>>> 5 and 0
0
Now the or returned the first item, and the and returned the second item. Also, they return numbers! Let's look at some combination of ints and strs and NoneTypes and bools:
>>> 5 and None and 3
>>> 3 or None or False
3
>>> None or 3 or 5
3
>>> None or False or 3
3
The first line evaluates to None, so you see nothing in the interpreter. But it seems like it returns a specific item.
I'll just tell you what is happening here. This is called short-circuiting, and what happens is that if we already know the result of the logical statement, why do we need to evaluate even further? For example, the evaluation of 5 and None and 3 is already known once we see the None, and the return of 3 or None or False is already known when we see 3. Here are the rules of short-circuiting
-
and: Return the first falsey value. Otherwise, return the last item. -
or: Return the first truthy value. Otherwise, return the last item.
You can see that and is like a mirror of or. Let me also say something about not
notwill change what ever it is being applied on to be thebooltypeTrueorFalse.
The reason for the not working this way instead of evaluating to the type's truth counterpart is that things like not 0 is nonsensical to find a counterpart. Is the counterpart 1, -1 , 100? Who knows? So we just change not 0 to True, and not "hello" to False.
Let's run through some more examples of short-circuiting: Try these out and check your answers with your Python terminal. I'll explain it below. Some of the statements will give you a ZeroDivisionError, so keep track of those too.
>>> 3 and 8 and 0
>>> 3 or 1/0
>>> "" or "." or ""
>>> False and None
>>> None or False
>>> not True and not False
We can combine both logical operators. You would need to make sure you simplify the expressions down as you see the parenthesis:
>>> 3 or (4 and 0)
>>> 3 and ((5 or 8) and (9 and None))
>>> 0 and (1/0 or 1/0 and 1/0)
After you have seen the solutions, read the solutions I wrote here:
-
3 and 8 and 0The rule here is to keep evaluating the next items until we reach the first falsey one. If all of them are truthy, we would then return the last item. We go through it one-by-one:3is truthy,8is truthy,0is falsey. This is the first falsey value, so we return0. -
3 or 1/0The rule here is to keep evaluating the next item until we reach the first truthy one. If all of them are falsey, return the last item. We just evaluate3since it is our first truthy value, so we return3. Note that1/0does not get read since we knew the result of the logical statement before we actually evaluated1/0. -
"" or "." or ""I'll start speeding up now. Since we are working withor, the first truthy value is".", so we return'"."' -
False and NoneThe first falsey value isFalse, so we returnFalse. -
None or FalseAll values are falsey, so we need to return the last item:False -
not True and not False. Each individual item becomesFalse and True. SinceFalseis the first falsey value, it evaluates toFalse.
Here are the solutions to the more involved compound logical statements:
-
3 or (4 and 0). We only read the first item to find the first truthy value, so it is3. We don't even have to read the parenthetical expression. -
3 and ((5 or 8) and (9 and None)):3is truthy, now we need to evaluate the second item, which is(5 or 8) and (9 and None). The first item of this subexpression, we need to evaluate.5 or 8evaluates to5, this is truthy, so we keep on going.9 and Noneevaluates toNone. Therefore, we are really trying to figure out5 and None, which results inNone. The main logical statement simplifies to3 and None, which isNone. -
0 and (1/0 or 1/0 and 1/0):0evaluates to falsey, so the whole statement results in0. Again, we do not have to evaluate all the1/0conditions because short-circuiting makes us stop once we know the answer.
Relational Operators¶
Relational operators evaluate to True or False. These are the actual bool types, not some alternative truthy/falsey values. A short list includes:
-
<less than -
>greater than -
<=less than or equal to -
>=greater than or less than -
==equal. Note that it has to be double equals, since we already dedicated a single equals=to be variable assignment.
A statement like 1 == 2 evaluates to False, and not 1 == 2 evaluates to True.
Anatomy of if¶
Now it's time to discuss the syntax of selection using the if clause. Similar to a function, you need 3 parts:
- the word
ifto indicate you are using selection - conditional statement(s). Here is where you use your condition making, truthy/falsey, short-circuity logic
- a suite to execute in the case that your condition is true. This can be things like assigning a value to a variable, returning a value, or even putting another if clause there.
In english, it might read as the following: "if
A general form might look like:
if <condition(s)>:
<do_the_following>
An example is checking whether a number is odd, the function I might implement could look like
def parity(n):
"""Returns 1 if n is odd, and 0 otherwise."""
if n % 2 == 1:
return 1
return 0
A couple things to think about:
-
returnfully stops the execution of your function. It will simply return the value that you put into the return statement. See how the above case has 2 differentreturns, but both do not get read; only one of them and then the function stops. -
the
piece of code must be indented so that Python knows what it should/should not run if the condition is true. -
I will be adding docstrings to each functions from now on to detail what each function will be accomplishing in high-level terms. Documentation, in general, is really good to do because it allows you and others to understand your code.
if, elif, else Chains¶
Now there are other things we can do with selection. Sometimes, the selection depends on separated conditions, not all under one. For example, if a number is a multiple of 3, do this, otherwise if the number is a multiple of 5, do this other thing, otherwise, the catch-all case is to do this other thing. For this we introduce the words elif and else. elif is just like another if statement, and else is the catch-all case. The pedagogical example is the fizzbuzz function. I'll modify its implementation to be the following: returns 'fizz' if the input is divisble by 3, buzz if the input is divisible by 5, 'fizzbuzz' if the input is divisible by 3 and 5, and returns None if no other case applies. I implemented the function below
def fizzbuzz(n):
"""Returns 'fizz' if the n is divisible by 3, 'buzz' if n is divisible by 5,
'fizzbuzz' if it is divisible by 3 and 5, and returns None for any other
case. """
if n % 3 == 0 and n % 5 == 0:
return "fizzbuzz"
elif n % 3 == 0:
return "fizz"
elif n % 5 == 0:
return "buzz"
else:
return None
Let me analyze this program:
- First question, does the order of the conditions matter? What if I did the divisible by 3 case before I did the divisible by 3 and 5 case? Well remember, return stops execution of the function and returns the value. So, putting the divisible by 3 case before all the other cases might produce incorrect results.
- In general, when chaining
if,elif, andelsestatements, you want to start with the most specific case, and then work your way towards the most general.
When putting all three selection statements, you can only end up evaluating one of the fizzbuzz specification to: print 'fizz' if the number is divisible by 3, 'buzz' if it is divisible by 5, 'fizzbuzz' if it is divisible by both 3 and 5, and prints None if no cases match; the function will return None. Again, I'll write it below.
def fizzbuzz(n):
"""Prints 'fizz' if the n is divisible by 3, 'buzz' if n is divisible by 5,
'fizzbuzz' if it is divisible by 3 and 5, and prints None for any other
case. Returns None"""
if n % 3 == 0 and n % 5 == 0:
print("fizzbuzz")
elif n % 3 == 0:
return print("fizz")
elif n % 5 == 0:
return print("buzz")
else:
print(None)
return
Again, let's analyze this function:
-
all inputs will go to the
returnstatement at the very end. For example, if I were to input3, I land on the case of printing'fizz'. Once I finish that I go to the end of our chain, where Ireturn. I could also just not write thereturnat all, or writereturn Noneexplicitly. All cases returnNone. -
The structure of chaining the selection makes it so only one
is evaluated. If I were to, say write "if" for each case and then an else in the following way:
def fizzbuzz(n):
"""Prints 'fizz' if the n is divisible by 3, 'buzz' if n is divisible by 5,
'fizzbuzz' if it is divisible by 3 and 5, and prints None for any other
case. Returns None"""
if n % 3 == 0 and n % 5 == 0:
print("fizzbuzz")
if n % 3 == 0:
return print("fizz")
if n % 5 == 0:
return print("buzz")
else:
print(None)
return
it would be wrong. Let's say I inputted 15 All division cases pass, so a Python session like
>>> fizzbuzz(15)
fizzbuzz
fizz
buzz
>>>
will end up like the above. That is because we didn't chain any statements as we did with the elif and else.
There is actually an interesting way to do this using string concatenation which does only if. I'll modify the implementation a slight in that the catch-all case will print out the empty string.
def fizzbuzz(n):
s = ""
if n % 3 == 0:
s += 'fizz'
if n % 5 == 0:
s += 'buzz'
print(s)
See how the ifs being separated in this way allows us to check both separated conditions. If I were to use an elif in the second case, only one of the two would execute its
Summary¶
In this note, we covered boolean logic, short-circuiting, relational operators, and the nuances among the if, elif, and else. These build the foundation of the idea of executing one's program with selection. In the next note, we will consider another way of control, known as iteration.
Go to next section: [[3.2 While Clauses]]
Get back to 3. Control or toc CS61A.