Python lets you write a range check the way you'd write it in maths: 36.1 <= temp <= 37.2. The rule behind it is simple, and it applies to every comparison operator, which is where the surprises come from.
The nice part
temp = 37.4 print(36.1 <= temp <= 37.2) hour = 14 print(9 <= hour < 17)
False True
No and, no repeating temp. The middle value is also evaluated only once, which matters when it's a function call.
The surprise
The rule is: a op1 b op2 c means a op1 b and b op2 c. It applies to ==, !=, is and in too, not just <:
print(1 < 2 == 2) print((1 < 2) == 2) x, y, z = 5, 9, 3 print(x < y > z) print(False == False in [False])
True False True True
1 < 2 == 2is1 < 2 and 2 == 2: true. With brackets,(1 < 2)isTrue, andTrue == 2is false.x < y > zis5 < 9 and 9 > 3. It says nothing about howxcompares withz.False == False in [False]isFalse == False and False in [False], not "isTruein the list".
Why it works
Chaining is part of the grammar, not a trick of <: every comparison operator has the same precedence, and a run of them expands into pairs joined by and, stopping at the first false one.
When not to use it
Keep chains to one direction (a <= b <= c, or a == b == c to check three values match). Mixed directions like x < y > z, or chains with in and is, read like something they're not: split them with and so the next reader doesn't have to work out the rule.