I ran a search on the codebase you pushed last week. Found fourteen instances of == True and nine of == False. Hold that number.
I knew you would find those. My previous team had a style rule: compare booleans explicitly. if flag == True reads like an English sentence. I thought it was safer than relying on implicit evaluation.
It reads like English. It does not read like Python. And it is not just style — it silently breaks the moment flag is not literally a boolean:
# Your version — narrower than you intend
if results == True:
display(results)
# Pythonic version — works for booleans, lists, strings, custom objects
if results:
display(results)["item"] == True is False. A non-empty list is never literally the boolean True. You have been writing a check that would never pass for a list, and you intended to check "does this list have data".
Wait — ["item"] == True is False? But the list has data, it is not empty. Why would that comparison fail?
Because == True is strict equality. Python compares the list object to the boolean True and they are different types. The truthy check if results: is completely different — Python calls __bool__ on the object, or __len__ if __bool__ is undefined. A non-empty list returns True from __len__. The complete falsy set is small and specific:
# Everything in this list evaluates to False in boolean context
falsy_values = [False, None, 0, 0.0, "", [], {}, (), set()]
# Everything else — including your non-empty list — is truthy
assert bool(["item"]) # True — non-empty list
assert bool("hello") # True — non-empty string
assert not bool([]) # True — empty list is falsy
assert not bool("") # True — empty string is falsySo if results == True: means "is results literally the boolean True" and if results: means "is results truthy" — which for a list means "is it non-empty." I have been asking the wrong question in fourteen places. The right question was always truthiness.
And truthiness extends to your own classes. Define __bool__ and your object knows how to answer if obj: without you having to write a comparison. Now — the or default pattern, which uses this same mechanism:
# Four lines for something Python can say in one
if name:
display_name = name
else:
display_name = "Anonymous"
# or returns the first truthy operand, or the last operand if all are falsy
display_name = name or "Anonymous"Python's or does not return True or False. It returns the actual operand. "hello" or "default" returns "hello". "" or "default" returns "default".
It returns the value itself, not a boolean. So config = user_config or defaults gives me the actual dict, not True. I have been writing four-line conditional assignments for this exact pattern when Python had a one-liner.
One trap before you refactor everything. When 0 or "" or [] are valid values you need to preserve, or will incorrectly replace them:
# Bug — user_count of 0 is valid but or replaces it
count = user_count or 0 # 0 or 0 returns 0, so the bug is silent
# Correct when 0 is meaningful data
count = 0 if user_count is None else user_countUse or when "falsy" and "missing" mean the same thing for your data. Use is None explicitly when zero, empty string, or empty list are valid values that need to survive.
The rule I need is: or for the cases where I genuinely want any falsy value replaced. is None for the surgical case where only the absence of a value should trigger the default. My config loader mostly uses or correctly, but there are two places where 0 is a valid timeout and I would be silently overwriting it.
Now the ternary expression — the last pattern for today. You have seen four-line if/else assignments everywhere:
# Four lines
if score >= 60:
label = "pass"
else:
label = "fail"
# One line — value first, condition second
label = "pass" if score >= 60 else "fail"The ternary is readable at one level. When you nest it — a if x else b if y else c — you are at the edge of what most readers can parse without stopping.
I have avoided ternaries because I thought they were a style trap — clever for the writer, confusing for the reader. But "pass" if score >= 60 else "fail" is perfectly clear. The rule is one level only?
One level is idiomatic. Two levels needs a comment or a function. Three levels needs a dict lookup or a chain of explicit returns. The test is: can the first-time reader parse it without slowing down? If not, the ternary is costing more than it saves. Now the last pattern — chained comparisons:
# The C way — two conditions, value evaluated twice conceptually
if low <= value and value <= high:
...
# The Python way — reads like the mathematical interval notation
if low <= value <= high:
...Python evaluates chained comparisons left to right and short-circuits. Each operator compares adjacent pairs. low <= value <= high checks both comparisons in one expression with value appearing once.
I have that and-joined pattern a dozen times in the data processing module. Every boundary check is two conditions when Python can write one. That is a specific refactor I can finish in twenty minutes — replace all of them with chained comparisons and the code reads like the specification it implements.
That is the Week 1 pattern: find where Python was designed to let you say more with less. Fourteen == True checks, a dozen boundary ands, and four-line default assignments — all clearer when you write what you mean.
__bool__, __len__, and Operator Short-CircuitingPython's truthiness protocol is not magic — it is a defined lookup sequence implemented in CPython's PyObject_IsTrue function. When Python evaluates if x:, it does not compare x to any value. It asks x to report its own boolean nature through a two-step protocol.
Step one: __bool__. Python calls type(x).__bool__(x). If the type defines __bool__, that return value determines truthiness. All built-in types that can be "empty" or "zero" define __bool__: int.__bool__ returns False for 0, float.__bool__ returns False for 0.0, str.__bool__ returns False for "". Custom classes that define __bool__ plug into this mechanism automatically — if my_obj: calls your __bool__ without any special registration.
Step two: __len__. If __bool__ is not defined, Python falls back to __len__. A return value of 0 maps to False; any non-zero value maps to True. This is why containers — lists, dicts, sets, tuples — are truthy when non-empty and falsy when empty, without each needing its own __bool__. The sequence protocol is enough.
Why == True silently fails for non-booleans. The == operator calls __eq__, not __bool__. [1, 2] == True invokes list.__eq__(True), which compares element-by-element and finds no match — the result is NotImplemented, which Python resolves to False. The list has truthiness but not equality-to-True. These are completely different questions: one asks "does this object have content?", the other asks "is this object the specific singleton True?"
The or and and short-circuit mechanics. Python's or and and operators do not produce booleans — they return operands. a or b evaluates a, calls __bool__ on it; if truthy, it returns a without evaluating b. If falsy, it returns b (again, the value itself, not its boolean). This is why name or "Anonymous" returns the string name when non-empty, not True. The short-circuit guarantees that b is never evaluated if a is truthy — useful for avoiding expensive computations or guarding against side effects.
Chained comparisons and evaluation order. low < value < high is not (low < value) < high. Python compiles it as low < value and value < high, but with value evaluated only once. The chain short-circuits: if low < value is False, value < high is never tested. This is both correct and efficient — a single expression with the semantics of the mathematical interval, not a workaround that happens to produce the right answer
Sign up to write and run code in this lesson.
I ran a search on the codebase you pushed last week. Found fourteen instances of == True and nine of == False. Hold that number.
I knew you would find those. My previous team had a style rule: compare booleans explicitly. if flag == True reads like an English sentence. I thought it was safer than relying on implicit evaluation.
It reads like English. It does not read like Python. And it is not just style — it silently breaks the moment flag is not literally a boolean:
# Your version — narrower than you intend
if results == True:
display(results)
# Pythonic version — works for booleans, lists, strings, custom objects
if results:
display(results)["item"] == True is False. A non-empty list is never literally the boolean True. You have been writing a check that would never pass for a list, and you intended to check "does this list have data".
Wait — ["item"] == True is False? But the list has data, it is not empty. Why would that comparison fail?
Because == True is strict equality. Python compares the list object to the boolean True and they are different types. The truthy check if results: is completely different — Python calls __bool__ on the object, or __len__ if __bool__ is undefined. A non-empty list returns True from __len__. The complete falsy set is small and specific:
# Everything in this list evaluates to False in boolean context
falsy_values = [False, None, 0, 0.0, "", [], {}, (), set()]
# Everything else — including your non-empty list — is truthy
assert bool(["item"]) # True — non-empty list
assert bool("hello") # True — non-empty string
assert not bool([]) # True — empty list is falsy
assert not bool("") # True — empty string is falsySo if results == True: means "is results literally the boolean True" and if results: means "is results truthy" — which for a list means "is it non-empty." I have been asking the wrong question in fourteen places. The right question was always truthiness.
And truthiness extends to your own classes. Define __bool__ and your object knows how to answer if obj: without you having to write a comparison. Now — the or default pattern, which uses this same mechanism:
# Four lines for something Python can say in one
if name:
display_name = name
else:
display_name = "Anonymous"
# or returns the first truthy operand, or the last operand if all are falsy
display_name = name or "Anonymous"Python's or does not return True or False. It returns the actual operand. "hello" or "default" returns "hello". "" or "default" returns "default".
It returns the value itself, not a boolean. So config = user_config or defaults gives me the actual dict, not True. I have been writing four-line conditional assignments for this exact pattern when Python had a one-liner.
One trap before you refactor everything. When 0 or "" or [] are valid values you need to preserve, or will incorrectly replace them:
# Bug — user_count of 0 is valid but or replaces it
count = user_count or 0 # 0 or 0 returns 0, so the bug is silent
# Correct when 0 is meaningful data
count = 0 if user_count is None else user_countUse or when "falsy" and "missing" mean the same thing for your data. Use is None explicitly when zero, empty string, or empty list are valid values that need to survive.
The rule I need is: or for the cases where I genuinely want any falsy value replaced. is None for the surgical case where only the absence of a value should trigger the default. My config loader mostly uses or correctly, but there are two places where 0 is a valid timeout and I would be silently overwriting it.
Now the ternary expression — the last pattern for today. You have seen four-line if/else assignments everywhere:
# Four lines
if score >= 60:
label = "pass"
else:
label = "fail"
# One line — value first, condition second
label = "pass" if score >= 60 else "fail"The ternary is readable at one level. When you nest it — a if x else b if y else c — you are at the edge of what most readers can parse without stopping.
I have avoided ternaries because I thought they were a style trap — clever for the writer, confusing for the reader. But "pass" if score >= 60 else "fail" is perfectly clear. The rule is one level only?
One level is idiomatic. Two levels needs a comment or a function. Three levels needs a dict lookup or a chain of explicit returns. The test is: can the first-time reader parse it without slowing down? If not, the ternary is costing more than it saves. Now the last pattern — chained comparisons:
# The C way — two conditions, value evaluated twice conceptually
if low <= value and value <= high:
...
# The Python way — reads like the mathematical interval notation
if low <= value <= high:
...Python evaluates chained comparisons left to right and short-circuits. Each operator compares adjacent pairs. low <= value <= high checks both comparisons in one expression with value appearing once.
I have that and-joined pattern a dozen times in the data processing module. Every boundary check is two conditions when Python can write one. That is a specific refactor I can finish in twenty minutes — replace all of them with chained comparisons and the code reads like the specification it implements.
That is the Week 1 pattern: find where Python was designed to let you say more with less. Fourteen == True checks, a dozen boundary ands, and four-line default assignments — all clearer when you write what you mean.
__bool__, __len__, and Operator Short-CircuitingPython's truthiness protocol is not magic — it is a defined lookup sequence implemented in CPython's PyObject_IsTrue function. When Python evaluates if x:, it does not compare x to any value. It asks x to report its own boolean nature through a two-step protocol.
Step one: __bool__. Python calls type(x).__bool__(x). If the type defines __bool__, that return value determines truthiness. All built-in types that can be "empty" or "zero" define __bool__: int.__bool__ returns False for 0, float.__bool__ returns False for 0.0, str.__bool__ returns False for "". Custom classes that define __bool__ plug into this mechanism automatically — if my_obj: calls your __bool__ without any special registration.
Step two: __len__. If __bool__ is not defined, Python falls back to __len__. A return value of 0 maps to False; any non-zero value maps to True. This is why containers — lists, dicts, sets, tuples — are truthy when non-empty and falsy when empty, without each needing its own __bool__. The sequence protocol is enough.
Why == True silently fails for non-booleans. The == operator calls __eq__, not __bool__. [1, 2] == True invokes list.__eq__(True), which compares element-by-element and finds no match — the result is NotImplemented, which Python resolves to False. The list has truthiness but not equality-to-True. These are completely different questions: one asks "does this object have content?", the other asks "is this object the specific singleton True?"
The or and and short-circuit mechanics. Python's or and and operators do not produce booleans — they return operands. a or b evaluates a, calls __bool__ on it; if truthy, it returns a without evaluating b. If falsy, it returns b (again, the value itself, not its boolean). This is why name or "Anonymous" returns the string name when non-empty, not True. The short-circuit guarantees that b is never evaluated if a is truthy — useful for avoiding expensive computations or guarding against side effects.
Chained comparisons and evaluation order. low < value < high is not (low < value) < high. Python compiles it as low < value and value < high, but with value evaluated only once. The chain short-circuits: if low < value is False, value < high is never tested. This is both correct and efficient — a single expression with the semantics of the mathematical interval, not a workaround that happens to produce the right answer