Python Truthiness, and/or and Short-Circuiting: 7 Tested Examples
Why does 0 or 10 produce 10? Why does "False" pass an if condition? Python conditions test truth values, not the English meaning of a word. This Proof Lab uses seven small, independently executed programs to separate truthiness, returned operands and short-circuit evaluation.
1. Definition: truthiness
A condition does not require an expression that already contains True or False. You can test a string, list or number directly. The built-in bool function gives the explicit Boolean result. Keep the value and its truth value separate: an empty list is not the Boolean False, but bool([]) is False.
2. How the decision flows
Test x. Truthy? Return x without evaluating y.
Falsy? Evaluate and return y.
x and y
Test x. Falsy? Return x without evaluating y.
Truthy? Evaluate and return y.
not operator always returns a Boolean.Follow the returned value
x or y x and y
| |
truth-test x truth-test x
| |
+-- truthy -> return x +-- falsy -> return x
+-- falsy -> evaluate y +-- truthy -> evaluate y
return y return y
Last operand: returned as-is, not converted to bool.
An enclosing if/bool(...) may truth-test that result.3. Falsy is not the same as missing
values = [None, False, 0, "", [], "0", "False"]
for value in values:
print(repr(value), bool(value))Checked output:
None False False False 0 False '' False [] False '0' True 'False' True
None, False, zero and empty containers are falsy. A non-empty string is truthy even when its text is "0" or "False". Truth testing does not parse the meaning of a word.
4. or returns an operand
print(repr("" or "guest"))
print(repr("Ada" or "guest"))
print(repr(0 or []))Checked output:
'guest' 'Ada' []
or returns its first operand if that operand is truthy; otherwise it returns the second operand. It does not automatically convert that result to True or False.
5. and also returns an operand
print(repr([] and "ready"))
print(repr([1] and "ready"))
print(repr("yes" and 0))Checked output:
[] 'ready' 0
and returns its first operand if it is falsy; otherwise it evaluates and returns the second operand. This explains why a logical expression can produce a list, a string or an integer.
6. Short-circuiting can avoid a call
def probe():
print("probe called")
return True
print(False and probe())
print(True or probe())
print(True and probe())Checked output:
False True probe called True
The first expression does not need its second operand because False already determines and. The second does not need its second operand because True already determines or. Only the final expression calls probe.
7. Preserve zero with an explicit None check
def bad_default(value):
return value or 10
def keep_zero(value):
return 10 if value is None else value
for value in [None, 0, 5]:
print(value, bad_default(value), keep_zero(value))Checked output:
None 10 10 0 10 0 5 5 5
Using or as a default treats every falsy value as absent. If zero is a valid setting, test is None so zero survives. Whether empty text is valid is a separate application decision.
8. Guard a division before evaluating it
for denominator in [0, 2]:
safe = denominator != 0 and 10 / denominator > 2
print(denominator, safe)Checked output:
0 False 2 True
and evaluates left to right. With denominator zero, the second expression is not evaluated, so no division by zero occurs. A guard must come before the operation it protects.
9. Use membership instead of the always-true trap
for choice in ["yes", "no", "maybe"]:
wrong = bool(choice == "yes" or "no")
right = choice in ("yes", "no")
print(choice, wrong, right)Checked output:
yes True True no True True maybe True False
choice == "yes" or "no" means (choice == "yes") or "no". The non-empty string "no" is truthy, so the wrong condition accepts every shown choice. Membership expresses the intended test clearly.
10. Quick comparison
| Expression | Returned value | Second operand evaluated? |
|---|---|---|
| x or y | x if x is truthy; otherwise y | Only when x is falsy |
| x and y | x if x is falsy; otherwise y | Only when x is truthy |
| not x | True or False | No second operand |
| bool(x) | True or False | No second operand |
11. Common mistakes and better choices
- Do not use
value or fallbackwhen zero, False or empty text is valid data. Decide what missing means and test it explicitly. - Do not assume that input containing "False" becomes False. input returns text; validate the text against the values your program accepts.
- Do not write
x == a or bwhen you mean either value. Usex in (a, b)or repeat the full comparison. - Do not place a dangerous call before its guard. Short-circuiting works left to right.
- Do not swap and/or with bitwise &/|. The operators have different purposes and evaluation behaviour.
Operator precedence also matters: not has higher priority than and, and has higher priority than or. Parentheses are often worth using when several tests are combined. For example, (is_student or is_teacher) and has_access states the grouping directly.
Boundary lab: precedence, selection and custom objects
The seven original programs and this separate lab were run with Python 3.10.12. No package installation is needed. Notice that a selected final operand is returned without an extra truth test: False or Bad() returns an object, but bool(Bad()) raises TypeError.
print('1. Precedence:', True or False and False)
print(' Regrouped:', (True or False) and False)
print('2. Legacy selection:', True and 0 or 10)
print(' Conditional:', 0 if True else 10)
print('3. Three operands:', repr('' or 0 or 'guest'))
print('4. Truth hooks:')
class Both:
def __bool__(self):
print(' __bool__ called')
return False
def __len__(self):
print(' __len__ called')
return 5
print(' result:', bool(Both()))
class Empty:
def __len__(self):
return 0
print(' len-only:', bool(Empty()))
class Plain:
pass
print(' no hook:', bool(Plain()))
print('5. Invalid truth hook:')
class Bad:
def __bool__(self):
return 1
try:
print(bool(Bad()))
except TypeError:
print(' TypeError: __bool__ must return bool')
print('6. Final operand is returned without a truth test:')
answer = False or Bad()
print(' returned Bad object:', isinstance(answer, Bad))Checked boundary output:
1. Precedence: True Regrouped: False 2. Legacy selection: 10 Conditional: 0 3. Three operands: 'guest' 4. Truth hooks: __bool__ called result: False len-only: False no hook: True 5. Invalid truth hook: TypeError: __bool__ must return bool 6. Final operand is returned without a truth test: returned Bad object: True
__bool__ must return True or False, not an integer such as 1. When that method is absent, __len__ supplies a non-negative integer length; zero makes the object falsy. Do not use the old condition and a or b selection pattern when a may be falsy: it can unexpectedly choose b. Use a if condition else b.
Truth-test decision table
| Object | What Python uses | Result |
|---|---|---|
| Both in the lab | __bool__; __len__ is not called | False |
| Empty in the lab | __len__ returns 0 | False |
| Plain in the lab | Neither hook exists | True |
| Bad in the lab | __bool__ returns an invalid int | TypeError |
| False or Bad() | Test False, then return the final operand | Bad object; no truth test on it yet |
12. Practice before checking
Predict bool("0"), 0 and "ready" and [] or [1]. Then run them. Answers: True, 0 and [1]. The text "0" is non-empty. The integer zero stops and immediately. The empty list makes or choose its second operand.
print(bool("0"))
print(0 and "ready")
print([] or [1])Checked practice output:
True 0 [1]
13. Exam questions with short answers
Q. What is truthiness?
The truth value an object has when tested in a condition.
Q. Name four built-in falsy values.
None, False, numeric zero and an empty list are examples.
Q. Is the string "False" falsy?
No. It is a non-empty string, so it is truthy.
Q. Does or always return True or False?
No. It returns one of its operands.
Q. When does and evaluate its second operand?
When the first operand is truthy.
Q. Why can value or 10 lose a valid zero?
Zero is falsy, so or chooses 10.
Q. How do you test only for a missing None value?
Use value is None, rather than a general truthiness test.
Q. Which truth hook has priority?
__bool__ first. Only when it is absent does Python use __len__. With neither, an instance is truthy by default.
Q. Does True and 0 or 10 select 0?
No, it returns 10. The first part yields 0, then or treats zero as falsy. Use 0 if condition else 10 for a real selection.
Q. Is the final operand always truth-tested?
No. It is returned as-is. Using that result later in if, not or bool() can trigger a truth test.
Comments
Post a Comment