Truthiness and Short-Circuit Evaluation in Python
22 Feb 2018In the high school Python class I’m helping out with, I’ve noticed that students will often write a chunk of code that looks like this:
In this example, the student has a num
variable whose value is some integer, and they’re trying to write some code that gets run if the integer is 5
or 6
or 7
. The code snippet above seems reasonable at first glance, but it actually does something completely different from what the student would expect.
Let’s focus on the num == 5 or 6 or 7
part, because that’s the part that isn’t doing what the student expects. Here’s what Python sees when you write that code:
I’m going to be using a lot of diagrams like this throughout this article. In these diagrams, a yellow box is a chunk of code that Python hasn’t evaluated yet. (“Evaluated” basically means “run”.)
Notice how the first yellow box in that diagram is
and the second box is
That second box isn’t num == 6
—it’s just 6
. That’s kind of weird! What does the number 6
do if you put it in an if
statement? Read on to find out!
OK, so we’re trying to decipher this code:
Let’s start our analysis by figuring out what that code does when the num
variable has the value 10
.
Python starts by evaluating 10 == 5
, which turns into False
.
So at this point, our partly-evaluated expression is False or 6 or 7
, and Python has to figure out whether or not that whole thing ends up evaluating to True
, because we’re running this code as the condition part of an if
statement.
What does Python do when it sees False or 6 or 7
? In order to answer that question, we’ll need to know about truthiness and short-circuiting.
Truthiness
You’re familiar with the values True
and False
. We call them “Booleans”, and we use them most often in if
statements.
hungry = True
if hungry:
print('try eating a slice of pizza')
else:
print('must be nice')
Python doesn’t limit us to just using True
and False
as the condition for if
statements, though—you can put any expression in there. If you put something in an if
statement’s condition section and it’s not True
or False
, Python will look at it and decide whether or not it’s “truthy”.
According to the official documentation, everything in Python is considered truthy except for these things:
False
None
0
- Empty sequences, e.g.
[]
,''
,()
,{}
You can use the built-in bool()
function to see if something is truthy. Here are some examples:
print(bool(True))
print(bool(False))
print(bool('cat'))
print(bool([]))
print(bool(['pizza', 'tacos']))
That code snippet is interactive, so go ahead and mess around with those examples to convince yourself that you understand how truthiness works. Is 15
truthy?
Now that we know what truthiness is, let’s talk about short-circuit evaluation.
Short-Circuit Evaluation
The and
and or
operators in Python are short-circuit operators. To see what this means, let’s look at an example use of the or
operator.
This is what Python sees before it starts evaluating that code:
Remember that if a box is yellow, that means that Python hasn’t evaluated it yet.
An or
expression is truthy if at least one thing in it is truthy. An and
expression is truthy if all things in it are truthy.
Since this is an or
, Python evaluates each of the yellow boxes in order until it finds one that’s truthy. It starts by evaluating 1 == 1
, which turns into True
.
At this point, Python stops, because you’re in an or
and it’s found something truthy! That’s what short-circuiting means. The whole or
expression evaluates to True
, because that’s the value of the first truthy thing in it.
Here’s how the official documentation describes or
’s behavior:
it only evaluates the second argument if the first one is false.
Here, I’ll prove it to you.
If you divide a non-zero number by zero, Python will throw an exception:
1 / 0
Now check out what happens if I put a 1 / 0
after a truthy thing in an or
:
print(True or 1 / 0)
The program prints True
and doesn’t evaluate the 1 / 0
! To convince yourself that this works the way I claim it does, try changing that True
to a False
.
This matches the behavior we saw in our most recent diagram. Do you remember how the 1 == 2
box stayed yellow to indicate that Python hadn’t evaluated the code inside of it?
So, that’s what “short-circuiting” means when you’re using the or
operator. The and
operator is pretty similar to or
, except that the official documentation says that and
only evaluates the second argument if the first one is true.
That makes sense, because and
wants to make sure that both of its operands are truthy. If the sub-expression on the left-hand side of an and
is falsey, then the whole and
expression is falsey! In that situation, there’s no need to evaluate the sub-expression on the right-hand side.
Here are some more examples. Do they all behave the way that you expect?
Back to our buggy num
code
Now that we know about truthiness and short-circuit evaluation, we can finally figure out what this code does!
What do you think will be printed out when that code is run?
Before we run it and find out for sure, let’s walk through one last set of diagrams using what we’ve learned. Here’s what Python sees before it starts evaluating anything:
Python begins by evaluating 10 == 5
, which turns into False
.
Next up, it evaluates 6
. We saw earlier that all non-zero numbers are truthy, so now our diagram looks like this:
At this point, Python stops and says: hey, I found something truthy! And that’s what the entire expression evaluates to. The answer is 6
!
num = 10
print(num == 5 or 6 or 7)
And so that’s why the code from the beginning of this article doesn’t do what our student expects. num == 5 or 6 or 7
will always evaluate to either True
or 6
, and so the code inside that if
statement will always be run!
num = 10
if num == 5 or 6 or 7:
1 / 0
else:
print('safe!')
Wrapping up
Here are a few more examples—play around with them and try adding some of your own!
print(False or [])
print(2 or False)
print(False or 0 or "hello")
Notice how if everything in an or
is falsey, then the whole or
expression will evaluate to the rightmost sub-expression.
print(False or 0)
If everything in an and
is truthy, then the whole and
expression will evaluate to the rightmost sub-expression.
print('cat' and 'dog')
Oh, and if you want to write some code that does what the student in our example actually wanted, try one of these:
num = 7
print(num == 5 or num == 6 or num == 7)
print(num in [5, 6, 7])
print(5 <= num <= 7)
By the way—what do you think this code does? Will it evaluate to True
? If not, why not?
Resources
- This walkthrough is great.
- So is this StackOverflow answer.
- @codewithanthony has this fascinating video about
False == False in [False]
.