Python Part I · Module 9 · Conditional Statements
What Counts as True: Truthiness
Chapter 9.1 slipped something past you. The verdict chain was written if fatals: rather than if fatals > 0:, and it worked — an integer sat where a comparison was expected and Python chose a branch from it without complaint. That is not a shortcut or a special case for numbers. Every value in Python can be used as a condition, and every value is treated as either true or false when it is. The rule behind that is short, designed, and worth understanding properly rather than memorising, because it produces the single most readable idiom in the language and also one genuinely dangerous ambiguity.
Foundation15 min readPythonPart IFundamentalsConditionalsTruthiness
Part I · Module 9 · Chapter 9.5 · What Counts as True: Truthiness
1. A Condition Need Not Be a Comparison
Every condition so far produced True or False by comparing something. This one does not:
errors = 4
if errors:
print("there were errors")there were errorserrors = 0
if errors:
print("there were errors")
else:
print("no errors")no errorserrors is an integer, not a Boolean. Python did not refuse it and did not convert the statement into a comparison. It asked a different question: does this value count as true?
That question has an answer for every value in the language, and bool() reports it directly:
print(bool(4))
print(bool(0))True
FalseChapter 9.1's model still holds exactly — evaluate the expression, get a truth value, select a path. What is new is that the truth value can come from interpreting an ordinary value rather than from a comparison producing a bool.
2. The Values That Count as False
The list is short. These are the ones you will meet:
print(bool(0))
print(bool(0.0))
print(bool(""))
print(bool([]))
print(bool(()))
print(bool({}))
print(bool(set()))
print(bool(None))
print(bool(False))False
False
False
False
False
False
False
False
False| Value | Type | Why it is false |
|---|---|---|
0 | int | zero |
0.0 | float | zero |
"" | str | empty |
[] | list | empty |
() | tuple | empty |
{} | dict | empty |
set() | set | empty |
None | NoneType | absent |
False | bool | false |
Everything else you have met counts as true.
3. The Values That Look False and Are Not
This is where the memorised version of the rule fails people:
print(bool("0"))
print(bool("False"))
print(bool(" "))
print(bool([0]))
print(bool([[]]))
print(bool(-1))True
True
True
True
True
TrueEvery one of those is true.
"0"is a string with one character in it. It is not empty, so it is true. The character happens to be a zero, which Python is not interpreting."False"is a string with five characters. Same reasoning. This is the classic trap when reading a configuration value from a file:if value:is true for the text"False"." "is a string containing a space — not empty.[0]is a list with one item. The list is not empty, so it is true, regardless of the item being falsy.[[]]is a list containing one empty list. The outer list has one item.-1is non-zero.
4. The Rule Behind the List
The table is not a set of special cases to memorise. One idea generates almost all of it:
A value counts as false when it represents nothing — zero, empty, or absent. Otherwise it counts as true.
Zero is the nothing of numbers. The empty string, list, tuple, dict and set are the nothing of their containers. None is the explicit absence of a value. Read the table again with that sentence in hand and it stops being arbitrary.
The practical consequence is that a condition can ask the natural question directly:
failed = []
if failed:
print("some tests failed")
else:
print("no failures recorded")no failures recordedfailed = ["reset", "crc"]
if failed:
print("some tests failed")
else:
print("no failures recorded")some tests failedif failed: reads as if there are any failures, which is exactly the question. The alternative, if len(failed) > 0:, computes a count and compares it to zero in order to ask whether a list has anything in it.
5. if errors: or if errors != 0:
Both work, and for a plain integer they select the same branch for every input. The choice is about which sentence the reader gets.
| Written | Reads as | Best when |
|---|---|---|
if errors: | if there were any errors | the value is a count or a collection, and zero/empty means nothing happened |
if errors != 0: | if the error count is not zero | zero is a meaningful value you are deliberately excluding |
if failed: | if anything failed | almost always, for collections |
if len(failed) > 0: | if the length is above zero | rarely — it says how to check, not what is being asked |
The honest guidance is not shorter is better. It is that the short form is better when the falsy case genuinely means "nothing". For a count of errors, a coverage list, a log line — it does, and if errors: is the clearer sentence.
6. When Zero Means Something
Here is the limit, and it is sharper than a style preference.
A dictionary records how many errors each test reported. One test ran and was clean. Another never ran at all.
counts = {"smoke": 0}
print(counts.get("smoke"))
print(counts.get("reset"))0
NoneThose are opposite facts: "smoke" has a recorded result of zero errors, and "reset" has no recorded result at all. Now ask about each with a truthiness condition:
counts = {"smoke": 0}
if counts.get("smoke"):
print("smoke has errors")
else:
print("smoke: branch not taken")
if counts.get("reset"):
print("reset has errors")
else:
print("reset: branch not taken")smoke: branch not taken
reset: branch not takenIdentical treatment. 0 is falsy and None is falsy, so a clean run and a missing run land in the same branch — and in a regression report, "passed cleanly" and "never ran" are the two things you most need to tell apart.
The separation requires asking the right question:
counts = {"smoke": 0}
print(counts.get("smoke") is None)
print(counts.get("reset") is None)False
TrueSo the three-case version says what it means:
counts = {"smoke": 0}
if counts.get("reset") is None:
print("reset has no recorded result")
elif counts["reset"] == 0:
print("reset was clean")
else:
print("reset reported errors")reset has no recorded resultThe same trap appears with positions, because 0 is a valid index:
position = 0
if position:
print("found at", position)
else:
print("treated as not found")treated as not foundThe item was found, at the very first position, and the program reported it as not found.
7. if ready == True: Is Not the Same Question
A Boolean variable needs no comparison at all:
ready = True
if ready:
print("ready")readyif ready == True: would work here, and it is redundant — ready is already the truth value, so comparing it to True produces the truth value again.
But the redundancy is not the real argument. The comparison and the truth test are different questions, and they disagree the moment the value is not a Boolean:
count = 3
print(bool(count))
print(count == True)True
Falseif count: is true. if count == True: is false, because 3 is not equal to True — although 1 is:
print(1 == True)
print(0 == False)True
TrueSo == True silently narrows a truthiness test into an equality test that passes only for True and 1. Write if ready: for a Boolean, and an explicit comparison for anything else.
8. Blank Lines, the Classic Use
The idiom that appears in every log-processing script in existence:
line = " "
if not line.strip():
print("blank line")
else:
print("has content")blank lineline = " UVM_ERROR "
if not line.strip():
print("blank line")
else:
print("has content:", line.strip())has content: UVM_ERRORThree ideas combine, and all three are already yours. .strip() removes surrounding whitespace (Chapter 4.7). A string with nothing left is empty, and an empty string is false. not inverts that, so the condition reads if there is nothing on this line but whitespace.
Note what if not line: alone would have done: a line of three spaces is not empty, so it would have counted as content. The .strip() is what makes the check mean blank rather than zero-length.
This condition becomes far more useful once Chapter 10.7 gives you a way to skip a line and move to the next one. The condition itself is finished; only its setting is missing.
9. Predict, Run, Observe
Say true or false for each, then check.
print(bool([]))
print(bool([[]]))
print(bool("0"))
print(bool(0.0))
print(bool(" "))
print(bool({"a": 0}))
print(bool(0) or bool("x"))
print(bool(None) == bool(0))The verified output:
False
True
True
False
True
True
True
True[]is empty — false.[[]]has one item — true."0"is a one-character string — true.0.0is zero — false." "has a character in it — true.{"a": 0}has one entry — true. The entry's value being falsy is not consulted.False or TrueisTrue.bool(None)andbool(0)are bothFalse, so comparing them givesTrue— which is exactly the collapse Section 6 warned about, written as an equality.
10. Exercises
Exercise 1 — the table from memory. Write down which of these are false, then verify all twelve with bool(): 0, 1, -1, "", " ", "0", [], [0], (), {}, set(), None.
Exercise 2 — say it out loud. For each, choose between if x: and an explicit comparison, and give your reason in one sentence: (a) errors is a count of UVM_ERROR lines (b) width is a signal width where 0 is invalid and must be reported (c) failed is a list of failing test names (d) name is a string read from a file that may be empty (e) index is a position in a list.
Exercise 3 — the configuration trap. A setting is read from a file as the string "False". Show that if setting: takes the true branch, then write a condition that treats the text "False" as off. Explain in one sentence why the first version cannot work.
Exercise 4 — clean or missing. Using counts = {"smoke": 0, "burst": 2}, write a program that prints one of three messages for "reset": no recorded result, clean, or a number of errors. Verify it for all three of "reset", "smoke" and "burst".
Exercise 5 — find the hidden zero. This is meant to report the first position of a failing test. Find the input for which it is wrong, and fix it.
position = tests.index("reset")
if position:
print("reset is at position", position)
else:
print("reset is not in the list")Exercise 6 — == True. For value = 1, value = 3 and value = True, give the result of both if value: and if value == True:. Explain the pattern in two sentences.
Exercise 7 — blank lines. Given the four strings "", " ", "\n" and "x", predict what if not line.strip(): does for each, then verify. Explain what .strip() contributes that if not line: would miss.
Exercise 8 — write the rule. In two sentences and without listing any values, state the rule that decides whether something counts as true in Python. Then name one value your rule gets wrong, or say why you believe there is none.
11. Summary
- Any value can be a condition. Python asks whether it counts as true, and
bool()reports that answer directly. - Falsy values:
0,0.0,"",[],(),{},set(),NoneandFalse. Everything else you have met is true. - The rule is one sentence: a value is false when it represents nothing — zero, empty or absent.
"0","False"," "and[0]are all true. Truthiness asks whether the container is empty and never looks inside it.if failed:reads as "if anything failed" and is better thanif len(failed) > 0:for exactly that reason.- Prefer the short form when the falsy case means "nothing happened", and an explicit comparison when it does not.
- Falsy is not missing.
0andNonetake the same branch, so a clean result and an absent result become indistinguishable — useis Noneto separate them. - A legitimate zero breaks the idiom: an error count of zero, a width of zero and position
0are all real values thatif value:discards. if ready:beatsif ready == True:, and not only for brevity —count == Trueis false forcount = 3, whichif count:is not.if not line.strip():detects a blank line, whereif not line:would only detect an empty one.
The next chapter takes the same decision and makes it produce a value instead of selecting statements.
Where this fits
Part of the Python curriculum.
