Truthiness and Short-Circuit Evaluation in Python

Target audience: beginner programmers

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 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:

num == 5
or
6
or
7

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

num == 5

and the second box is

6

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.

False
or
6
or
7

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:

1 == 1
or
1 == 2

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.

True
or
1 == 2

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?

1 == 2
and
2 == 2
1 == 1
and
1 == 2
1 == 1
and
2 == 2
1 == 2
or
1 == 1

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:

num == 5
or
6
or
7

Python begins by evaluating 10 == 5, which turns into False.

False
or
6
or
7

Next up, it evaluates 6. We saw earlier that all non-zero numbers are truthy, so now our diagram looks like this:

False
or
6
or
7

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

If you've found an error or have a suggestion, please open an issue or pull request on GitHub.