A chained comparison evaluates adjacent relations in order and stops when one relation is false.
Python comparison chains: the middle operand is evaluated once
Operation contract
The receipt amount is read through a function that records each call. The expression zero less than amount less than or equal to a cap calls that function once. The explicit and expression also calls it once here because its second comparison uses a saved local value. Repeating a function call on both sides of and would read mutable state twice and could compare two different observations.
Failure and ownership boundary
Comparison chains do not freeze the object returned by the middle expression; mutation inside a custom comparison method can still change its state. An input validator should reject bool explicitly when integer amounts are required, since bool inherits int. Python numeric operators: division, floor division and remainder contracts and Python positional-only and keyword-only parameters: keep call roles explicit cover adjacent rules.
Working program
reads = []
def read_amount():
reads.append("ledger")
return 125
print(0 < read_amount() <= 500)
print("reads:", len(reads))
amount = read_amount()
print(0 < amount and amount <= 100)
print("reads:", len(reads))Output
True
reads: 1
False
reads: 2Costs and limits
The comparisons themselves are bounded for small integers. Calling an expensive or stateful middle expression once matters more than syntax length. Custom ordering methods and unbounded integers have their own cost.
Common Mistakes
- Do not translate a chain into two calls to a stateful getter.
- A chained comparison is not a transaction over mutable operands.
Connected lessons
Python numeric operators: division, floor division and remainder contracts, Python rich comparison: return NotImplemented for unsupported operands, Python positional-only and keyword-only parameters: keep call roles explicit.
