Telling The Truthy
There's more to true and false in Python than the Boolean `True` and `False`.
I always tell the truth. Of course I do! I'm even uncomfortable with those untruths we tell children.
"Does Father Christmas exist?"
"What do you think? I never actually saw him but there are presents under the tree on Christmas morning. How do you think they got there?"
There's nothing untrue in my response, you see.
And sometimes, there are untruths that educators say to learners—not because they want to deceive or trick them but because the truth is too complex. The simpler "untruth" is easier to explain.
When I'm teaching a beginners' course, and I'm introducing the if
statement or the while
loop, I find myself using phrases such as:
"
if
needs to be followed by something that Python understands as true or false."
Of course, it's simpler to say that if
must be followed by True
or False
. But that would be an untruth. The if
keyword can be followed by any object or expression. An expression evaluates to an object, and Python can 'understand every object as true or false' when used in a Boolean context. We use the quaint terms truthy and falsy to describe whether Python understands an object as true or false. I'll clarify these terms a bit later.
A few weeks ago, I asked about your views on displaying code in these articles. The options were either using images with properly formatted code with syntax highlighting (with links to GitHub Gists for longer ones to facilitate copy-paste) or Substack’s native option, with no control on formatting or syntax highlighting. The overwhelming majority of those who responded chose the properly formatted image snippets. And I agree.
I’m trying a variation of this setup starting from this article. The code is still in the same image snippets (created using Snappify, the brilliant tool by
and Anki). However, instead of linking longer blocks to a Gist, I have an appendix with all the code in Substack’s native code blocks at the end of the article, which you can use to copy and paste (although I recommend typing the code in directly–it’s better and probably quicker!) Each code block is numbered, so you can easily find the one you need at the bottom.Truthiness and Falsiness
Let's start with the built-in bool()
, which you can use to convert any object into a Boolean.
We tend to think of bool()
as a function. However, bool
is the class name. Therefore, bool()
is the constructor that creates an instance of bool
from its argument. But this is just a detail, and it's not essential for our discussion here. So let's move on.
Let's look at a few short examples to confirm this:
Every integer except for 0
is converted to True
when you cast it to a Boolean using bool()
. Therefore, every non-zero integer is truthy and 0
is falsy. We can extend this idea to all numeric types, not just integers. For example, 0.0
is falsy but any other float is truthy.
When Python needs to 'understand an object as true or false', it will use the value returned by bool()
to determine what to do.
For objects that have a length (these are called sized objects), the object is falsy if its length is 0
and truthy for any non-zero length:
You can try this with other sized objects such as strings, tuples, dictionaries, and sets. These data structures are falsy when they're empty and truthy when non-empty.
And there's another object that's always falsy: None
.
User-Defined Classes, Truthiness, and Falsiness
How about other objects that are not numeric and don't have a length?
Let's create a simple class and test it:
Every object is truthy by default. However, you can override this default behaviour. You can determine an object's truthiness or falsiness by defining the __bool__()
special method in the class definition:
The class now has a __bool__()
special method. In this example, this special method always returns False
. All instances of this class will now be falsy.
The __bool__()
special method can have some additional logic, of course:
You added the __init__()
special method and a data attribute .person
. Instances of this class are now truthy if the person's name is "Stephen"
and falsy for any other name. I realise this is a touch too egocentric! Apologies.
But there's another way to control truthiness or falsiness without defining the __bool__()
special method. You can define __len__()
, which makes the objects sized—they have a length:
You've set the length of a TestTruthiness
object to the same value as its .person
data attribute—in this case, this is the only attribute this class has!
In this example, the objects representing people with names "Stephen"
and "Jane"
are both truthy. All names are truthy. But if the empty string is used instead of a person's name, the TestTruthiness
object is falsy.
Some Other 'Unexpected' Consequences of Truthiness and Falsiness
Let me start with an example:
What's unexpected about this? We've seen that first_list
is falsy since it's an empty list. And second_list
is truthy since it's not empty. You use an expression with or
in the if
statement, and since one of the lists is truthy, the if
block is executed.
But what does the expression first_list or second_list
return? No, it doesn't return True
:
The expression starts with first_list
, which is falsy. The or
expression is looking for at least one truthy value. Therefore, it moves on to the object after the or
keyword, second_list
. And this is truthy. So, the or
expression returns this object. It doesn't need to return True
since it returns a truthy value, and that's good enough!
What if the truthy object came before the or
keyword?
You get the same output. But in this case, the first object is truthy and or
only needs one truthy object. So, the or
expression doesn't bother looking at what comes after the or
keyword.
Do you want proof?
And why is this 'weird'? Because the expression after the or
should raise an error:
The string "hello"
can't be converted into an integer. But when int("hello")
is used after the or
keyword in second_list or int("hello")
, the expression after the or
is never executed.
And what happens if both objects in an or
expression are falsy?
The first object is checked first. The integer 0
is falsy. The expression is looking for at least one truthy object. So, the second object is checked. The empty list []
is also falsy. But the expression doesn't need to return False
. Instead, it returns the last element, which is falsy. That's good enough in any Boolean context in Python.
Let's summarise:
The
or
expression returns the first object if it's truthy.But if the first object is falsy, the second object is returned, whatever the second object is. If the second object is falsy, then the whole expression is falsy. But if the second object is truthy, then the whole expression is truthy.
We can follow a similar logic with and
. An and
expression needs both objects to be truthy. So, if the first object is falsy, the expression returns this first object immediately, without evaluating the second object:
There's no error raised in the second of these examples since the first object is falsy and returned immediately.
But when the first object is truthy, the and
expression moves to whatever comes after the and
keyword. This raises an error.
If the first object is truthy in an and
expression, the second object is always returned, whether it's truthy or falsy:
This type of behaviour with or
and and
is known as short-circuit evaluation. Now it's your turn to play with or
and and
expressions and truthy and falsy objects.
And Some Facts About True
And False
This article is not about the Boolean data type and its two instances, True
and False
. But let me finish with a bit of trivia about these objects:
The bool
class is a subclass of int
. Therefore, True
is equal to 1
and False
is equal to 0
. This is why True + True
is the same as 1 + 1
(and I don't need to tell you that gives 2
). Multiplying by False
is the same as multiplying by 0
. And since dictionaries must have unique keys, when you create the key True
, it overrides the value of the previous key 1
since True
is equal to 1
.
Note that True
and 1
are not the same object, but they have the same value. True is 1
gives False
, which shows they're not the same object. But True == 1
gives True
, showing they are equal.
Final Words
How complex can the notion of true or false be? When you extend the definition of true and false to include truthiness and falsiness, there's a bit more than meets the eye.
I hereby confirm that, to the best of my knowledge, I have provided you with the truth, the whole truth, and nothing but the truth in this article.
Code in this article uses Python 3.12
Stop Stack
#49
If you like my style of communication and the topics I talk about, you may be interested in The Python Coding Place. This is my platform where I have plenty of video courses (with plenty more coming over the coming months), a community forum, weekly videos, and coming soon, live workshops and cohort courses. Any questions, just reply to this email to ask.
If you read my articles often, and perhaps my posts on social media, too, you've heard me talk about The Python Coding Place several times. But you haven't heard me talk a lot about is Codetoday Unlimited, a platform for teenagers to learn to code in Python. The beginner levels are free so everyone can start their Python journey. If you have teenage daughters or sons, or a bit younger, too, or nephews and nieces, or neighbours' children, or any teenager you know, really, send them to Codetoday Unlimited so they can start learning Python or take their Python to the next level if they've already covered some of the basics.
Recently published articles on The Python Coding Stack:
My Neighbours Are Moving House • Mutating The Immutable Tuple (Sort Of) Tuples are immutable, but their contents can change. Eh?! Let me tell you the story of my neighbours moving house, and all will make sense
Put On Your Deerstalker. You're Now a Detective. It's Time For Debugging Debugging Python Code Is Like Detective Work
Why Can't I Just Use A List? • Understanding NumPy's
ndarray
(A NumPy for Numpties article) From Python's native lists to NumPy'sndarray
data type, with a glimpse at the built-inarray
. Why do we need all these similar data structures?next(years) An end-of-year post • Some reflections • And there's some Python stuff in this post, too—a spinning globe animation
Do Not Try This At Home A bit of silliness for the holiday season • But please, don't code like this. Please • Plus some out-of-the-norm commentary • There's nothing ordinary about today's article
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
The South Park Technical Writing Manual (Ep. 14) What can we learn from South Park? Yes, the satirical TV show
I Haven't Been Abducted by Aliens (Ep. --) Why this long lull since the last Breaking The Rules post?
The Selfish Reason (Ep. 13) Another reason for authors to innovate • Enjoying the writing process
The Consequential Detail (Ep. 12). Can a single letter or one blank line make a difference? (Spoiler Alert: Yes)
The Unexpected Audience (Ep. 11). What I'm learning from listening to Feynman's physics lectures
Stats on the Stack
Age: 9 months, 2 weeks, and 1 day old
Number of articles: 49
Total subscribers: 1,909
On the Paid tier: 95
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles. Alternatively, if you become a member of The Python Coding Place, you'll get access to all articles on The Stack as part of that membership. Of course, there's plenty more at The Place, too.
Appendix: Code Blocks
Code Block #1
# Auto-generated from an interactive REPL/Console session
if 5:
print("Yay")
else:
print("Nay")
# Yay
if 0:
print("Yay")
else:
print("Nay")
# Nay
bool(5)
# True
bool(0)
# False
Code Block #2
# Auto-generated from an interactive REPL/Console session
if [1, 2]:
print("Yay")
else:
print("Nay")
# Yay
if []:
print("Yay")
else:
print("Nay")
# Nay
len([1, 2])
# 2
bool([1, 2])
# True
len([])
# 0
bool([])
# False
Code Block #3
# Auto-generated from an interactive REPL/Console session
class TestTruthiness:
pass
test = TestTruthiness()
if test:
print("Yay")
else:
print("Nay")
# Yay
bool(test)
# True
Code Block #4
# Auto-generated from an interactive REPL/Console session
class TestTruthiness:
def __bool__(self):
return False
test = TestTruthiness()
if test:
print("Yay")
else:
print("Nay")
# Nay
bool(test)
# False
Code Block #5
# Auto-generated from an interactive REPL/Console session
class TestTruthiness:
def __init__(self, person):
self.person = person
def __bool__(self):
return self.person == "Stephen"
bool(TestTruthiness("Stephen"))
# True
bool(TestTruthiness("Jane"))
# False
Code Block #6
# Auto-generated from an interactive REPL/Console session
class TestTruthiness:
def __init__(self, person):
self.person = person
def __len__(self):
return len(self.person)
bool(TestTruthiness("Stephen"))
# True
bool(TestTruthiness("Jane"))
# True
bool(TestTruthiness(""))
# False
Code Block #7
# Auto-generated from an interactive REPL/Console session
first_list = []
second_list = [5, 10]
if first_list or second_list:
print("At least one of the lists is not empty")
# At least one of the lists is not empty
Code Block #8
# Auto-generated from an interactive REPL/Console session
first_list or second_list
# [5, 10]
Code Block #9
# Auto-generated from an interactive REPL/Console session
second_list or first_list
# [5, 10]
Code Block #10
# Auto-generated from an interactive REPL/Console session
second_list or int("hello")
# [5, 10]
Code Block #11
# Auto-generated from an interactive REPL/Console session
int("hello")
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# ValueError: invalid literal for int() with base 10: 'hello'
Code Block #12
# Auto-generated from an interactive REPL/Console session
0 or []
# []
Code Block #13
# Auto-generated from an interactive REPL/Console session
0 and 10
# 0
0 and int("hello")
# 0
Code Block #14
# Auto-generated from an interactive REPL/Console session
10 and int("hello")
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# ValueError: invalid literal for int() with base 10: 'hello'
Code Block #15
# Auto-generated from an interactive REPL/Console session
10 and [2, 3]
# [2, 3]
10 and []
# []
Code Block #16
# Auto-generated from an interactive REPL/Console session
True + True
# 2
25 * False
# 0
some_dict = {1: "This is the integer 1", True: "This is the Boolean True"}
some_dict[1]
# 'This is the Boolean True'