How Python Evaluates Chained Comparisons
In Python, chained comparison operators like
1 < x < 10 provide a clean, mathematical syntax for
evaluating multiple conditions simultaneously. Rather than evaluating
expressions strictly from left to right as independent boolean
operations, Python transforms chained comparisons into combined logical
conditions using an implicit and. This article explains the
exact evaluation model Python uses, including how intermediate operands
are evaluated only once and how short-circuit evaluation applies.
The Logical Transformation
When Python encounters a chained comparison in the form:
a op1 b op2 cit treats it as logically equivalent to:
(a op1 b) and (b op2 c)In traditional programming languages like C, Java, or JavaScript,
writing 1 < x < 10 causes the left expression
1 < x to resolve first to a boolean (True
or False, or 1 or 0). That
boolean is then compared to the next operand (evaluating
True < 10 or False < 10), which produces
unexpected bugs. Python avoids this by treating comparisons as chained
operators at the grammar level.
Single Evaluation of Operands
While a < b < c behaves logically like
(a < b) and (b < c), there is a critical distinction:
Python guarantees that intermediate operands are evaluated only
once.
Consider this example with a function that produces a side effect:
def get_number():
print("Function called!")
return 5
result = 1 < get_number() < 10Output:
Function called!
If Python simply expanded the expression to
1 < get_number() and get_number() < 10,
get_number() would execute twice. Instead, Python
internally caches the result of the middle operand:
# Conceptual representation of internal execution
temp = get_number()
result = (1 < temp) and (temp < 10)This ensures performance is optimized and prevents unintended bugs when evaluating complex expressions, database queries, or generators.
Short-Circuit Evaluation
Because chained comparisons expand with the semantics of the
and operator, they inherit Python's short-circuit behavior.
Evaluation stops immediately as soon as any comparison in the chain
evaluates to False. Subsequent comparisons and expressions
are skipped entirely.
def expensive_operation():
print("This will not run.")
return 15
# The first comparison (5 < 2) is False
result = 5 < 2 < expensive_operation()In this case:
5 < 2evaluates toFalse.- Python short-circuits.
expensive_operation()is never called, andresultevaluates toFalse.
Chaining Across Different Comparison Operators
Chaining is not restricted to standard inequalities
(<, <=, >,
>=). Python allows any comparison operator to be chained
together, including equality checks (==, !=),
identity checks (is, is not), and membership
tests (in, not in).
For example:
a == b == cchecks ifa,b, andcare all equal.x is not None and 0 <= x < 100can be written as0 <= x < 100ifxis guaranteed to be comparable.x < y in my_listevaluates to(x < y) and (y in my_list).
While syntactically valid, mixing completely different categories of operators (such as inequalities with membership tests) is often discouraged in production code because it can reduce readability for other developers.