Truthiness, None, and Identity in Python
Choose between a truth test, equality, and identity without treating valid zero values as missing.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Truthiness, None, and Identity in Python
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
A score of zero may be disappointing, but it is still a real score. If our program labels it as "Not graded," it has changed the meaning of the data. This happens often when an optional number is represented by None and a programmer reaches for a short condition such as if not score. The condition looks readable, and it even works for positive scores and the missing-value case. The defect appears only at the boundary, where a valid number is false in a Boolean context. We will run the broken function, inspect how Python evaluates several values, and make a small repair based on the function's actual contract. Then we will test more than the two comfortable cases.
- 00:42
Keep one question in mind throughout: which value means absent, and which values merely happen to be false? On the companion website, you can run each example, repair the checked exercise, and try the quiz yourself. The first line is correct: None means that no score has been recorded. The last line is correct too: eight becomes a score label. The middle line reveals the bug. Python treats integer zero as false when an if statement asks for a truth value. The condition does not ask whether the object is None. It asks whether the object behaves as false. That group includes zero, False, an empty string, and empty containers.
- 01:23
The function has quietly merged several distinct states into one. A similar bug appears in a default expression such as score or 100: a legitimate zero would be replaced by one hundred. Before changing the code, describe the intended behavior without syntax. We want the missing-value label only for the specific sentinel None. Every other supplied value should be rendered as a score. That statement points directly to the comparison we need. The middle column is the truth value used by if score. None, zero, and False all produce false there. The last column answers a narrower question: is this object the None sentinel?
- 02:06
Only None produces true. This distinction matters because equality also has a surprise: False equals zero numerically in Python, yet they are not interchangeable as data labels. An identity check with None communicates the exact rule. Use is None when the contract says None means missing, and is not None when you need the opposite. That does not mean truth tests are bad. If the requirement is "does this list contain any elements," if items is concise and appropriate. The error comes from using a broad truth test for a narrow condition. Notice how a tiny table of edge cases gives us better evidence than a single successful positive example.
- 02:49
The repair is deliberately small. We changed the question from "is this value false?" to "is this value None?" The rest of the function already expresses the desired behavior: return a formatted label for anything that was supplied. The zero score now survives. Keeping the second return outside an else block is a style choice; the first branch returns immediately, so execution can only reach the second return for a non-None value. In the website exercise, the starter function contains the broad truth test. Make this one-line change, then run the checker rather than judging the code only by the two prepared print statements.
- 03:30
The checker probes zero, a positive number, None, and a couple of other false values. That catches a tempting alternative such as score == False, because zero compares equal to False. The exact sentinel test expresses the contract more clearly than a clever-looking shortcut. These assertions cover the meaningful categories rather than five arbitrary numbers. None is the absent sentinel, zero is a valid but false integer, False is a different false object, the empty string is another false value, and eight is a straightforward positive case. The function renders all supplied values because that is its stated contract.
- 04:12
In a production grade book, you might separately validate that a score is an integer within a range. That would be another rule, with its own error behavior, and should not be smuggled into this missing-value check. Imagine the score arrives from a form or a CSV file. A blank field might initially be an empty string, while a missing database value might be None. Decide which of those states your application actually means by "not graded" before writing the condition. If you normalize blank text to None at an input boundary, document that conversion and test it there. Otherwise, keep the states separate. A truth test inside score_label would silently make that decision for every caller, which is exactly how the zero bug appeared.
- 04:58
This is the debugging habit to take away: reproduce the failing boundary, name the exact condition, make the narrowest repair, and test the nearby values that could be confused with it. Try replacing the score with an empty list in the browser console and predict the label before you run it. Then compare a truth test with an identity check in a different optional-value function of your own.
Understand the concept
Python decides whether an object is true in an if statement by asking for its truth value. Zero, False, empty strings, and empty containers are false; nonempty strings and containers are generally true. That convenience is useful when you mean 'has any items', but it is the wrong test when zero is a valid measurement. An optional score of zero is still a recorded score.
None is a single sentinel for 'no value' in many Python APIs. Write value is None when that exact sentinel is the condition. Write value is not None for the opposite. Identity asks whether two references point to the same object; equality asks whether their values compare equal. Because False == 0 is true in Python, testing score == False is especially misleading when scores are numeric.
A default selected with value or fallback uses the fallback for every false value, including 0 and the empty string. If only None means missing, use a conditional expression based on is None instead. A function can deliberately accept 0 while rejecting None. State that contract explicitly, then verify both cases rather than testing only positive values.
The exercise uses None as the only missing-value marker. It returns 'Not graded' for None and formats every other supplied value, including zero, False, and an empty string. Those last two values are deliberate edge cases, not a claim that they are valid scores in a real grade book. Do not special-case each value: one identity check followed by an f-string is enough.
- if value uses truthiness
- None is tested with is None
- False == 0 does not mean they are identical
- or fallback replaces all false values
See it step by step
Read the code, predict the output, then compare it with the result.
01. Compare equality and identity
zero = 0
print(False == zero)
print(False is zero)
print(None is None)True
False
TrueFalse and zero compare equal numerically, but they are different objects. The None sentinel is checked by identity.
02. Preserve a measured zero
def label(value):
return 'missing' if value is None else f'value={value}'
print(label(None))
print(label(0))
print(label(4))missing
value=0
value=4Only the sentinel triggers the fallback. A measured zero remains part of the result.
03. See why or is not a None default
for value in (None, 0, '', 5):
print(repr(value), '->', value or 'fallback')None -> fallback
0 -> fallback
'' -> fallback
5 -> 5The or expression selects the right operand whenever the left operand is false, regardless of why it is false.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Reproduce a missing-value mistake
An optional score has two different kinds of state: no score has been recorded, or a score has been recorded and happens to be zero. The first state is represented by None in this example. The second is the integer zero. A broad truth test does not preserve that distinction because Python considers both values false in an if statement.
Run the broken code and compare the three calls. It appears to work for None and eight, which explains why this bug can survive a quick demonstration. Only the boundary value zero exposes the mismatch between the code and the data contract. Before editing anything, write the expected label for each input beside the observed label.
def score_label(score):
if not score:
return 'Not graded'
return f'Score: {score}'
for value in (None, 0, 8):
print(score_label(value))Not graded
Not graded
Score: 8- None correctly produces the missing-value label.
- Zero incorrectly takes the same path because its truth value is false.
- A positive integer takes the formatted-score path.
Compare truth, equality, and identity
Truth testing asks whether a value behaves as true or false. Equality asks whether two values compare equal under their types' comparison rules. Identity asks whether two references point to the same object. Those questions can produce different answers for the same input. False and zero compare equal numerically, yet they are distinct objects and neither is None.
The table in this example makes the difference visible. The last column is the one the score function needs: only None should be called absent. If your requirement were instead to skip empty text or an empty list, a truth test could be correct. Choose the test from the stated requirement, not from how short the syntax looks.
for value in (None, 0, False, 8):
print(repr(value), bool(value), value is None)
print(False == 0)None False True
0 False False
False False False
8 True False
True- The Boolean column groups None, zero, and False together.
- The identity column selects only None.
- Equality between False and zero is true, so equality with False is no substitute for the sentinel check.
Keep every supplied value
Replace the broad truth test with an identity check. The early return handles the one missing marker; the remaining return formats any supplied value. This keeps zero without adding a special branch for it. The same pattern works for optional counts, measurements, and settings when None is the only absence marker.
The practice task checks more than a positive score. It includes False and an empty string to make sure the implementation did not simply replace one broad test with another broad test. This function formats those values because validation is outside its present contract. If a real grade book should reject them, add a separate validation rule and test that rule explicitly.
def score_label(score):
if score is None:
return 'Not graded'
return f'Score: {score}'
for value in (None, 0, False, ''):
print(repr(value), '->', repr(score_label(value)))None -> 'Not graded'
0 -> 'Score: 0'
False -> 'Score: False'
'' -> 'Score: '- Only the None branch returns Not graded.
- Every other supplied object is formatted without changing its meaning.
- Compare the zero line with the broken result before completing the checker.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Replace the truth test with if score is None: return 'Not graded'. Then return the formatted label on the remaining path.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Treating a valid zero as absent
if not score:
return 'Not graded'if score is None:
return 'Not graded'What happens: The genuine score zero is labeled as missing.
Test the sentinel specified by the data contract, not every false value.
Using or for a None-only default
score = supplied or 100score = 100 if supplied is None else suppliedWhat happens: A supplied zero becomes 100.
The explicit condition preserves all supplied values other than None.