Python truthiness, explained
if my_list: reads naturally, but it relies on Python's truthiness rules — and those
rules trip people up around empty containers, and/or return values, and custom objects.
This guide covers what's falsy, how and/or actually behave, and how explicit conversion
works.
What counts as falsy
Any object can be tested in a boolean context. Python treats these as falsy:
bool(None) # False
bool(False) # False
bool(0) # False — also 0.0, 0j
bool("") # False — empty string
bool([]) # False — empty list, also (), {}, set()
bool(range(0)) # False — empty range
Everything else is truthy: non-empty containers, non-zero numbers, and any object that doesn't say otherwise.
Prefer truthiness over explicit comparisons
Because empty containers are falsy, the Pythonic way to check for "has items" is the object itself:
items = []
if items: # idiomatic — checks "non-empty"
process(items)
if len(items) > 0: # works, but verbose and un-Pythonic
process(items)
One important exception: when a value could legitimately be 0 or "" and you need to
distinguish it from "missing", check is None explicitly:
def f(timeout=None):
if timeout is None: # NOT `if not timeout:` — 0 is a valid timeout!
timeout = 30
How objects decide their own truthiness
For a custom class, Python checks __bool__ first; if it's absent, it falls back to
__len__ (zero length = falsy); if neither exists, the object is always truthy.
class Cart:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
bool(Cart([])) # False — falls back to __len__ == 0
bool(Cart([1, 2])) # True
class Always:
def __bool__(self):
return False
bool(Always()) # False — __bool__ wins
and / or return operands, not booleans
This surprises people: and and or return one of the operands, not True/False.
They short-circuit:
"a" and "b" # 'b' — and returns the last value if all truthy
"" and "b" # '' — and returns the first falsy value
"a" or "b" # 'a' — or returns the first truthy value
"" or "b" # 'b' — or returns the last if all falsy
This enables the classic default-value idiom (with the same 0/"" caveat as above):
name = user_input or "Anonymous" # use input, or fall back if empty
For a true boolean negation, not always returns an actual bool: not "" → True.
Explicit type conversion
Python doesn't do implicit numeric→string coercion ("x" + 1 is a TypeError), so you
convert explicitly with the type constructors:
int("42") # 42
int("42", 16) # 66 — parse as base 16
int(3.9) # 3 — truncates toward zero (not rounding!)
float("3.14") # 3.14
str(42) # '42'
list("abc") # ['a', 'b', 'c']
dict([("a", 1)]) # {'a': 1}
bool([]) # False
int() on a float truncates toward zero, so int(-3.9) is -3. Bad input raises
ValueError (int("abc")), which is what you catch when validating user data.
Recap
Falsy values are None, False, numeric zero, and empty containers/strings; everything
else is truthy. Write if items: rather than if len(items) > 0:, but use is None
explicitly when 0 or "" are valid values you must distinguish from "missing". Custom
objects control truthiness via __bool__, falling back to __len__. Remember that
and/or return an operand and short-circuit, powering the value or default idiom.
Conversions are explicit — int(), float(), str(), list() — and int() on a float
truncates toward zero.