Truthiness and Short-Circuit Evaluation in Python22 Feb 2018
In 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
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
Python starts by evaluating
10 == 5, which turns into
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
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.
You’re familiar with the values
False. We call them “Booleans”, and we use them most often in
hungry = True if hungry: print('try eating a slice of pizza') else: print('must be nice')
Python doesn’t limit us to just using
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
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:
- 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
Now that we know what truthiness is, let’s talk about short-circuit evaluation.
or operators in Python are short-circuit operators. To see what this means, let’s look at an example use of the
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.
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
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
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
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
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
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
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
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
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
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!')
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?