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!