Python Gotcha: 'in'-Operator Precedence

Here's a Python 'gotcha' I spotted in this StackOverflow question the other day. It's not something that's ever bitten me, but I thought it was interesting enough to write a quick post about.

Anyone who's used Python for a good length of time has probably come across the idea of operator chaining. In languages like C, to check that a value was in a certain range, you'd do something like this:

if ((low <= x) && (x < upper)){  
    // Do something here!
}

It's fairly concise, but it gets ugly pretty quickly if there's more than a couple of conditions.

In Python, it's a lot simpler - just use the mathematical style of 'chaining' operators:

if low <= x < upper:  
    # Do something here!

The documentation for operator precedence can be found here. The operators in, not in, is, is not, <, <=, >, >=, != and == all have the same precedence which gives some strange results, like the one referenced in the StackOverflow question above.

Consider the expression x in y == True, where x is some object such that x in y evaluates to True and y is some object such that y != True.

The obvious, wrong answer is that the expression evaluates to True. In fact:

>>> x in y
True  
>>> x in y == True
False  

This assumption turns out to be false. The logical next step is that there's an operator precedence issue - but we've just ascertained that in and == have the same precedence. Instead of correcting it immediately, we can try to reproduce the error more clearly:

>>> x in (y == True)
Traceback (most recent call last):  
  File "<stdin>", line 1, in <module>
TypeError: argument of type 'bool' is not iterable  

Makes sense - but it raises the question of how exactly the original expression is evaluating to False, instead of either evaluating to True or raising a TypeError.

The answer lies in the chaining demonstrated above: what's really happening is this:

(x in y) and (y == True)

Since y is presumably some sequence (or custom object with a __contains__ method), we know it's definitely not equal to True.

To get the result we want, of course, we just put the first expression in brackets:

(x in y) == True

Although, in practice, it's not worth checking for equality to a boolean - just checking if the result is truthy or falsy will likely be enough:

if x in y:  
    # Do something!

I've never once written - or personally come across, besides that SO question - any code that chains operators of that precedence other than the "limit" operators. It's hard to think of where x in y in z would ever come in handy, although I'd concede that x != y != z might be a neat way to save a few characters if you're code-golfing (with the spaces removed, of course!).

Edit: I just came across another possibility for where chaining mixed operators could come in handy:

if x < f() != y:  
    # Do something!

We save some screen space by eliminating a variable for the two checks against the function's return value - f() is only evaluated once, whereas the following would evaluate it twice:

x < f() and f() != y  

But really, minimising vertical space shouldn't be of a higher priority than code clarity, and this works just as well:

result = f()  
if x < result and result != y:  
    # Do something!