Quick summary
Summarize this blog with AI
x and y first tests the truth value of x. If x is falsy, it returns x without evaluating y. Otherwise, it evaluates and returns y. The returned value does not have to be a Boolean. Use it for scalar control flow; use elementwise operators for pandas arrays. Examples were checked with Python 3.12.14 and pandas 2.2.3/3.0.6.
Observe which operand is returned
for left, right in [(0, "next"), ("ready", 7), ([], 10), (True, False)]:
result = left and right
print(repr(result), type(result).__name__)
print(bool("ready" and 7))
Expected output:
0 int
7 int
[] list
False bool
True
Common falsy values include None, False, numeric zero and empty built-in containers. A custom object can implement its own truth testing. Use bool(...) only when the API actually needs a Boolean; otherwise preserve the meaningful operand. A chain a and b and c stops at its first falsy operand or returns the last operand.
Prove skipped work and use a valid guard
visits = []
def probe(value):
visits.append(value)
return value
print(probe(0) and probe(99), visits)
visits.clear()
print(probe(2) and probe(3), visits)
def starts_with_sql(text):
return isinstance(text, str) and text.startswith("sql")
print([starts_with_sql(x) for x in [None, "sqlpad", "SQL", 3]])
Expected output:
0 [0]
3 [2, 3]
[False, True, False, False]
The type check prevents a method call on None or an integer. It does not validate every possible business rule. In particular, value is not None and 10 / value still divides by zero when value is zero. Decide whether zero is valid and handle it explicitly. Avoid hiding important side effects in a Boolean expression; an ordinary if statement is easier to inspect.
Group scalar conditions by meaning
active, paid, admin = False, False, True
print(active and paid or admin)
print(active and (paid or admin))
Expected output:
True
False
and binds more tightly than or. The first rule permits an admin regardless of active status; the second requires active status for everyone. Parentheses should reflect the requirement, not merely silence an error. For “all elements satisfy a test,” use all; for “at least one,” use any. Remember that all of an empty iterable is True while any of an empty iterable is False.
Use elementwise masks for pandas
import pandas as pd
values = pd.Series([2, 12, None], dtype="Int64")
try:
(values > 0) and (values < 10)
except ValueError:
print("ambiguous Series truth value")
mask = ((values > 0) & (values < 10)).fillna(False)
print(mask.tolist())
print(values.loc[mask].tolist())
print(all([]), any([]))
Expected output:
ambiguous Series truth value
[True, False, False]
[2]
True False
Both sides of & are evaluated, so it cannot guard an unsafe scalar function call. Nullable mask values are explicitly filled with False here to exclude unknowns. To ask one scalar question of a Series, choose the appropriate .any() or .all() and its missing-value policy. Do not reduce to a scalar merely to work around an error when the real task is row filtering.
References and next steps
Boolean operations, pandas Boolean indexing, Operator reference.