Boolean Logic, Truthy and Falsy Values
Every Python object is either truthy or falsy, the falsy list is short and closed, and knowing it lets conditions read as plain English.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
True and False
True and False are the only two values of type bool. They are singletons: every True in a program is the same object.
print(type(True)) # <class 'bool'>
print(True + True) # 2, because bool subclasses int
print(int(False)) # 0Truthiness
Python does not require a condition to be a boolean. Any object can be tested, and Python asks the object whether it considers itself true.
The complete falsy list
| Category | Falsy values |
|---|---|
| The booleans | False |
| Nothing | None |
| Zero | 0, 0.0, 0j, Decimal(0), Fraction(0) |
| Empty sequences | "", (), [], b"", range(0) |
| Empty collections | {}, set(), frozenset() |
Everything else is truthy. That is not a simplification; the list above is the whole of it.
print(bool(0), bool(0.0), bool(""), bool([]), bool({}), bool(None))
# False False False False False False
print(bool(-1), bool(0.1), bool("0"), bool("False"), bool([0]), bool(" "))
# True True True True True TrueLook carefully at"0","False",[0]and" ". All four are truthy. A string containing the word False is a non empty string; a list containing zero is a non empty list; a single space is a character.
How Python decides
For an object of your own class, Python calls __bool__ if it exists. If not, it calls __len__ and treats a length of zero as false. If neither exists, the object is truthy.
class Basket:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
empty = Basket([])
full = Basket(["pen"])
print(bool(empty)) # False, because len() is 0
print(bool(full)) # True
if not empty:
print("nothing to check out")This is why an empty list is falsy: list defines __len__. There is no special case in the language for containers; it falls out of one protocol.
Writing conditions the Python way
names = []
# Verbose
if len(names) == 0:
print("empty")
# Idiomatic
if not names:
print("empty")
value = None
# Verbose
if value != None and value != "":
print("has a value")
# Idiomatic
if value:
print("has a value")When explicit is better
def apply_discount(percent=None):
if percent is None: # correct: 0 is a legitimate discount
percent = 10
return percent
def apply_discount_wrong(percent=None):
percent = percent or 10 # wrong: a discount of 0 becomes 10
return percent
print(apply_discount(0)) # 0
print(apply_discount_wrong(0)) # 10Use truthiness when "empty or missing" are the same case. Use is None when zero, an empty string or False are meaningful values in their own right. That single distinction prevents a whole family of quiet bugs.
The logical operators as value selectors
print(0 or "fallback") # fallback
print("set" or "fallback") # set
print("a" and "b") # b
print(None and "b") # Noneorreturns the first truthy operand, or the last one if none are truthy.andreturns the first falsy operand, or the last one if none are falsy.notalways returns a realTrueorFalse.
any and all
scores = [55, 72, 48, 90]
print(any(s >= 80 for s in scores)) # True - at least one
print(all(s >= 40 for s in scores)) # True - every one
print(any([])) # False - nothing is true
print(all([])) # True - nothing is falseThe two empty cases catch people out. all([]) is True because there is no counterexample. It is the mathematically correct answer, and it is occasionally exactly the wrong one for your program, so guard the empty case explicitly when it matters.
fields = {"name": "Meera", "email": "", "phone": "9000000000"}
missing = [key for key, value in fields.items() if not value]
print(missing) # ['email']
print("Form complete" if all(fields.values()) else "Incomplete")Comparison chains and boolean results
age = 30
is_working_age = 18 <= age < 60
print(is_working_age) # True, a real bool
# Assign the condition to a name instead of repeating it.
if is_working_age and not is_retired:
...Common mistakes
- Writing
if x == True:. Writeif x:. The comparison form also breaks for truthy non boolean values, since1 == Truebut2 == TrueisFalse. - Believing
bool("False")isFalse. - Using
orfor defaults where0or""is valid data. - Forgetting that
all([])isTrue. - Writing
if len(items) > 0:whenif items:says the same thing. - Testing
if value != None:instead ofif value is not None:.
Best practices
- Use truthiness for emptiness, and
is Nonefor absence. - Name a complicated condition and use the name; conditions with three or more clauses are hard to read inline.
- Prefer
anyandallwith a generator expression over a loop that sets a flag. - Return real booleans from predicate functions, so callers get a predictable type.
Practice
- Sort these into truthy and falsy from memory, then verify:
0,"0",[],[[]],{},{"": 0}," ",None,"None",0.0,-1,range(0). - Write a class whose instances are falsy when a field is empty, using
__bool__. - Explain the difference between
if not value:andif value is None:with an example where they disagree. - Rewrite a flag setting loop using
any. - Explain why
all([])isTrueand give a case where you would need to check for emptiness separately.
Conclusion
The falsy set is short: false, none, zero, and anything empty. Everything else is true. Once that list is memorised, Python conditions stop needing comparisons and start reading like sentences - and you will know the one place, distinguishing empty from missing, where the explicit test is still required.