Chained Comparisons
Range checks are everywhere in a pipeline: a latency between 0 and 500 ms, an hour between 0 and 23, a timestamp inside a partition's window. Python lets you write 0 <= hour <= 23 the way a math textbook would, and it means what the textbook means.
Python reads a < b < c as a < b and b < c, with one difference: the middle operand is evaluated once. A chain can be as long as you like, and it is True only when every adjacent pair is True. Any comparison operator you know can join a chain, == and != included, and is too.
| Chain | What Python checks | Result when x = 7 |
|---|---|---|
| `0 <= x < 10` | `0 <= x and x < 10` | True |
| `10 > x > 3` | `10 > x and x > 3` | True |
| `0 < x < 5` | `0 < x and x < 5` | False |
| `x == 7 == 7.0` | `x == 7 and 7 == 7.0` | True |
| `1 < x > 3` | `1 < x and x > 3` | True |
latency_ms = 412.5
print(0 <= latency_ms < 500)
print(100 <= latency_ms < 400)
hour = 24
print(0 <= hour <= 23)
print(0 <= hour < 24)Output:
True False False False
Half-open ranges tile without gaps
hour <= 23 and hour < 24 give the same answer for every whole hour, which is why 24 failed both. They stop agreeing on values with fractions. The form low <= x < high, called half-open, includes its lower bound and excludes its upper one, so adjacent bands written this way share a boundary without overlapping: every value lands in exactly 1 band. Daily partitions follow the same rule, so an event stamped exactly at midnight belongs to the new day and never to both.
With the closed bands on the left, a latency of 99.5 is too big for fast and too small for normal, so it matches no band at all. Closed bands that share a number fail the other way. With ms <= 100 and 100 <= ms, 100 qualifies for both, and only the order of the branches decides the label.
Chains that read wrong
print(5 > 3 > 1)
print((5 > 3) > 1)
print(3 > 2 == True)
print(1 != 2 != 1)Output:
True False False True
Parentheses end a chain: (5 > 3) > 1 computes True first and then compares it with 1, and True behaves as 1 in arithmetic and comparisons, so 1 > 1 is False. 3 > 2 == True chains into 2 == True, which is False. 1 != 2 != 1 comes out True even though its first and last values are equal, because a chain only compares neighbours and says nothing about its ends.
Each link needs comparable types
Every link in a chain is a real comparison, so each pair must support ordering. A value read from a CSV or a web form arrives as a string, and a chain that puts it next to a number stops at the first link. If you convert the value with int("85") where it enters, the chain works on numbers from then on.
A row's score field arrived from a web form. What does this program do?
score = "85"
if 0 <= score <= 100:
print("valid")
else:
print("out of range")- It prints out of range, because a string never falls inside a numeric range.
- It prints valid, because Python converts "85" to 85 for the comparison.
- It prints valid, because "85" sorts between 0 and 100 as text.
- It stops with a TypeError, since Python 3 cannot order the int 0 against a str.
About this lesson
This section is part of the Control Flow: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.