10.2 Truthiness Rules: Every Object Has a Boolean Value
In Python, every object inherently has a Boolean value, a concept formally known as its “truthiness.” This value is not arbitrary; it is determined by a well-defined set of rules that the language applies consistently. Understanding these rules is fundamental to writing correct and predictable conditional logic, as the if, while, and and/or operators all rely on this truthiness evaluation rather than checking for an explicit True or False.
The mechanism behind this is the bool() built-in function. When Python needs to determine the truthiness of an object x in a context like if x:, it effectively calls bool(x). This function, in turn, relies on the object’s __bool__() method. If that method is not defined, Python falls back to the __len__() method. An object is considered “truthy” if bool(x) returns True and “falsy” if it returns False.
The Core Falsy Values
A small, finite set of built-in objects are considered falsy. All other objects are truthy. This list is crucial to memorize:
NoneFalse(obviously)- Zero of any numeric type:
0,0.0,0j(complex number) - Empty sequences and collections:
'',(),[],{},set(),range(0) - Objects for which
__bool__()returnsFalseor__len__()returns0
falsy_values = [None, False, 0, 0.0, 0j, '', (), [], {}, set(), range(0)]
for value in falsy_values:
if not value:
print(f"'{value}' is falsy.")
# Output: Every single value in the list will be printed.
Custom Objects and Truthiness
For user-defined classes, you can control their truthiness by defining the __bool__ or __len__ magic method. Python checks for __bool__ first. If it’s not implemented, it then checks __len__. If neither is implemented, the object is always considered truthy.
class BankAccount:
def __init__(self, balance):
self.balance = balance
# Define truthiness based on a condition (balance > 0)
def __bool__(self):
return self.balance > 0
# __len__ is not defined, so it's not used.
account_1 = BankAccount(100.00)
account_2 = BankAccount(0.00)
if account_1:
print("Account 1 has a positive balance.") # This will print.
if account_2:
print("Account 2 has a positive balance.") # This will NOT print.
Common Pitfalls and Edge Cases
A frequent source of bugs is assuming that non-boolean objects will be evaluated in a way that aligns with human intuition rather than Python’s strict rules.
Pitfall 1: Checking for an empty list. The correct, Pythonic way is to rely on its falsy nature. Writing if not my_list: is preferred and more readable than if len(my_list) == 0:.
Pitfall 2: Numeric checks. This is a critical edge case. Any non-zero number is truthy, including negative numbers. This can be counterintuitive if you are only thinking about “positive” meaning “true.”
def check_energy(energy_level):
if energy_level: # This is a BUG if energy_level is -5
print("Energy is present!")
else:
print("No energy.")
check_energy(100) # Output: "Energy is present!"
check_energy(-5) # Output: "Energy is present!" (Likely incorrect logic)
check_energy(0) # Output: "No energy."
# The correct, explicit way to check for a positive value:
def check_energy_fixed(energy_level):
if energy_level > 0:
print("Energy is present!")
else:
print("No energy.")
Pitfall 3: The None singleton. A common pattern is to check if a variable is None to see if an operation failed or a value was not set. Because None is falsy, a compound check can go wrong.
result = None # Imagine this came from a function that might return None or a list
# DANGEROUS: This will execute if result is an empty list OR if it is None.
if not result:
print("No results found.")
# SAFER: This explicitly checks for None, allowing empty lists to be handled separately.
if result is None:
print("The query failed.")
elif not result: # Now this safely checks for an empty list
print("The query returned no items.")
else:
print(f"Found {len(result)} items.")
The key distinction is to use is None or is not None when your intent is to specifically check for the None singleton, as it is an identity check, not a truthiness check.
Best Practices
- Embrace Implicit Truthiness: Use
if container:andif not container:for checking if sequences and collections are empty. It is the idiomatic and cleanest approach. - Be Explicit with Numbers: When checking numeric values, explicitly compare against zero (
if value > 0:,if value != 0:) to avoid errors with negative {{< bibleref “Numbers 3 ” >}}. UseisforNone: Always useif x is None:orif x is not None:to check for the presence or absence of theNonevalue. Never rely on truthiness for this specific check. - Document Custom Behavior: If your custom class’s
__bool__method has non-obvious logic (e.g., aCarobject being truthy only if it has fuel and is started), document this behavior clearly.