Avoiding the hazards of Python truthy values
- software development
A truthy value is a value that is considered True when used in a boolean context. A falsy value is a value that is considered False when used in a boolean context. This is a useful feature of Python and several other languages.
They are very useful for writing concise and uncluttered code. But they come with a hidden danger to trap the unwary.
These are falsy values:
- A zero number (int, real or complex).
- A zero-length string, list, tuple, dictionary or set.
- The None object.
- The False object.
These are truthy values:
- A non-zero number (int, real or complex).
- A non-zero length string, list, tuple, dictionary or set.
- The True object.
- Almost all other objects that aren't in the falsy list.
Using truthy values
Here are some examples of how truthy values can make your code a little clearer. Consider this code:
n = 1 if n != 0: print("n is not zero")
This can be made a little neater using truthy values. If
n is not zero it counts as true, so the if clause will be executed:
n = 1 if n: print("n is not zero")
Here is some code that only prints a list if it is not empty:
k = [...] if len(k)!=0: print(k)
This can also be tidied up using truthy values. If the list is not empty, it will count as true so the list will be printed:
k = [...] if k: print(k)
Finally, this code will prompt the user to enter their name, but only if the variable
name is empty:
name = ... if len(name)==0: name = input("What is your name?")
This code does the same thing using truthy values:
name = ... name = name or input("What is your name?")
To understand how this works, notice that the code uses an or expression, with two values
The code first evaluates
name is true (ie a non-empty string), then Python immediately knows that the or expression will be true, because true or-ed with anything is true.
Python doesn't need to call the
input function because it already knows that the result will be true. In fact, Python guarantees that it won't evaluate the second expression if the first one is true. This is called [short-circuit evaluation(https://pythoninformer.com/python-language/intermediate-python/short-circuit-evaluation/). Python will just immediately return the truthy value it has - the initial value of the
name is initially an empty string? Well in that case Python doesn't know whether the
or expression will be true or false, so it must evaluate the second part of the or expression. This means that if
name is empty it must call the
input function. The user will be prompted to enter their name, and whatever they type in will be returned as a truthy value and assigned to
Truthy values may take a bit of getting used to if you haven't met them before, but they are generally a good thing. They make your code less cluttered and often avoid unnecessary comparisons or calls to
The dark side
Consider this code:
import math def checked_sqrt(x): try: return math.sqrt(x) except ValueError: return None answer = checked_sqrt(4) if answer: print(answer) else: print("Error calculating square root")
We define a function
checked_sqrt that calls the
math.sqrt function to calculate a square root.
math.sqrt function will throw a
x is negative because negative numbers don't have a square root. Our function handles this case by returning
None to indicate that no value could be calculated.
We then call
checked_sqrt, and check the answer. If we call the function with a value of 4, the function will calculate the square root as 2.0, which is a truthy value, so the if statement will print the answer.
If we call
checked_sqrt with a value of -4, an exception will occur and the function will return
None. That is a falsy value, so the if statement will print an error message.
That is all fine. But what if we pass in a value of 0?
math.sqrt will calculate the square root, which of course is 0.0. Since there was no exception,
checked_sqrt will return the value of 0.0.
But when we execute the if statement,
answer is 0.0, which is also a falsy value (just line
None). This means that our code will incorrectly print the error message even though the square root has been calculated correctly.
So what is the problem here? The basic problem is that checking the truthy value of
answer cannot distinguish between the cases of
None and 0.0.
The deeper problem is that Python uses dynamic typing, which means that
checked_sqrt can return different types of data (a float or a
None object) under different circumstances. If we tried this code in Java, for example,
checked_sqrt would be declared to return a float so returning null would not be possible (it would give a compiler error).
This is compounded by code misdirection. The function explicitly returns
None, but the fact that it could also return a different falsy value of 0.0 is not immediately obvious. A quick glance at the code might have you convinced (wrongly) that
None is the only possible falsy return value.
Firstly, it is worth remembering that, in many cases, this will not be a problem. For example, if you have a local variable that is declared with a string value, and is never used in a way that could possibly result in it holding anything other than a string value, then it is perfectly OK to use its truthy value to test if it is empty.
This is true if the variable accepts a return value from a function that only returns a string value. It is true if the value is a parameter of the function that should only ever be a string value. For the purposes of testing the string is empty, the value is supposed to be a string, so it is valid to use its truthy value. The value is not supposed to be
None so you can ignore that possibility.
If you are concerned that the value might not, in fact, be a string, then that is a separate issue. That should be enforced using type hints, runtime type checks, unit tests, code reviews and so on. But in the main body of your function, you should assume that the value is of the type it is supposed to be.
So this problem only applies in situations where a value might legitimately have different types. The example above, where the variable can either valid a valid value of
None, is a very typical situation.
One solution is very simple. Don't use truthy variables for these cases. Instead, explicitly check if the value is
if answer is not None: print(answer) else: print("Error calculating square root")
The solution is easy, the tricky part is training yourself to always be on the lookout for this type of problem.
The second solution is to avoid the problem altogether by not allowing the function to return an ambiguous result. For example:
- The function could always return a number, but that number could be >=0 to indicate a valid result, or -1 to indicate an invalid result.
- The function could always return a tuple.
(True, value)would indicate that the
(False, value)would indicate that there is no valid result, and
valueshould be ignored.
- In a similar vein, use optionals.
An optional is a special object that can hold a value that may or may not be present. It includes methods to check if the value is present, to get the value if it is present, and other useful features. It is basically a cleaner alternative to the informal value-or-none protocol.
Truthy values are a very useful feature of Python, but they can create ambiguity and bugs in certain circumstances. This article included several solutions to this problem that may be applied depending on the specific circumstances.
Sign up to the Creative Coding Newletter
Join my newsletter to receive occasional emails when new content is added, using the form below: