Truthy and Falsy Gotchas
Think back to when you wrote your first ever
if statement in Python. I’m sure that intuition told you to only give Python boolean expressions that naturally evaluates to
2 + 2 == 4.
But sooner, rather than later, you find yourself testing a list or a string’s length because it’s once again intuitive to you and, in other programming languages, possibly the only way to do so:
Before long, however, you learn that it’s un-Pythonic: that there’s a better way, a shorter way. You learn that Python will evaluate just about anything in a boolean context given the opportunity and if you squint your eyes it all makes sense.
A boolean context is anything where Python expects nothing but
False, such as an
What is Truthy and Falsy?
The terms truthy and falsy refer to values that are not ordinarily boolean but, when evaluated in a boolean context – like inside an
if statement – they assume a
False value based on characteristics inherent to the value of the object you are giving it. For example: the number zero, empty strings and lists evaluate to
False; but non-zero numbers, non-empty lists and lists are
In this example Python knows that
items is a list and that it is truthy if it contains at least one item, and that it is falsy if it contains zero. There’s no need to check if the list is equal to an empty list, or that it has a length of 0. Python “does the right thing.”
But what about
Ah. Yes. Well it evaluates to
False, so that’s the end of that, right:
Weeell, no. So it just evaluates to False — like an empty list or string, or the number zero. So if you test an expression in an
if statement and it is
None it gets turned to
False and the world keeps ticking and nothing bad ever happens.
In this (slightly contrived) example there’s a function
get_age, that returns the age of a person it knows about. If it does not find anyone with that name it returns
In other words,
get_age has a return type of
Optional[int]. Meaning, it can be either: an integer value and the age of the person; or
None, to indicate that no such person exists.
But why test for
is None as I do in the
if-statement if the shorthand falsy check works fine? A naive interpretation of idiomatic Python discourages you from explicitly testing for falsy or truthy values with the understanding that it will sort it out for you. But let’s run the code and observe what happens:
$ python age.py Selma's age is 0
Which is correct. Selma is not yet 1 year of age. But if I replace the
if age is None check with
if not age:
$ python age.py There is no one with the name Selma
And now our application has a subtle logic error. The reason is that we accept multiple falsy values but treat them as though they were one:
Nonewhen it does not recognize the name of someone.
get_agereturns an integer value when it does recognize the name of someone. However, if their age happens to be
0, then Python converts it to
The mistake here is coalescing two distinct conditions (
0) and treating them as though they were one. This is a very common anti-pattern in Python that you see repeated everywhere. Even when where there is, admittedly, no risk of mistakes:
re.match function returns either a match object or
None, so there is no risk of coalescing two distinct falsy values here. But it’s not correct; it just so happens that it works fine. You should mind the
None values that you encounter, but it is something more honored in the breach than in the observance.
I am going to end this with an actual example of where your intuition and blindly relying on Python to do the right thing will put you in hot water:
This is a simple example that generates a little XML document and then reads two elements from it: one that exists and one that is missing.
If you run the code you get some surprising results:
$ python etree.py The element does not exist! Element bool value: False Element is None: False Missing element bool value: False Missing element is None: True
The element that exists evaluates to
False – as the condition for it ever being
True is when it has one or more child elements – but the missing element is
None which is also
False (in a boolean context). The only way to test that the element is missing is with a
None check because
None if it cannot find the missing element.
And therein lies the danger of falsy checking. Depending on your viewpoint the
ElementTree works as intended: an
Element is only ever evaluated as
True if, and only if, it has at least one child element. For all other conditions it is
False. So the proper way to check whether an element exists is with
is not None.
One last remark. If you use this library then you must not write
if statements that depend on
True only if it has children. Another developer – unaware of this unintuitive behavior – may instead assume it checks for existence.
- Using truthy and falsy checks is perfectly fine
But you must consider that if you take something that is inherently not a
Falsevalue and try to make it so, that you carefully consider what it maps to. Letting Python figure out what’s
Trueor not is almost always fine – notwithstanding the XML example above – and unlikely to get you into trouble, but if you have to interface with poorly-written third-party libraries or if you are tasked with defining whether a compound object maps to
Falseyourself you should exercise great care.
It is infact just
None. If your code interacts – as it so often will – with
Nonethen you must test for that explicitly with
is not None.
Elementobject from above perfectly illustrates the danger of not testing for existence with
None. If you can make your code explicit and obvious then you should always prefer to do so.
- Everything evaluates to
If you cram something through a fine-meshed, boolean sieve you’re going to end up with
False. But that does not mean it’ll give you the intuitive answer you like. You’re trading the state of a possibly-complex object with something that is rather binary. Make sure you understand what that trade-off implies.
get_ageexample taught you that
Falseyet one represents the absence of age and the other a literal age. And the XML snippet taught you that your intuition may not match the intent of the developers who wrote the code in the first place.