An Interesting Statement#

I saw a very strange statement once that took me a little while to unpack what was going on. It has the following format:

value_1 is value_2 in [list_values]

Testing Order of Operations#

In order to understand what the order of operations was for the above I made some test cases that should help me understand which is being performed first.

First Test Case#

I first looked at the following example:

False is False in [False, True]

Running this example through the Python interpreter yields a result of True

>>> False is False in [False, True]
True

This matches the expectation if is is manually separated with parenthesis vs if in is separated to be performed first:

>>> (False is False) in [False, True]
True
>>> False is (False in [False, True])
False

Therefore, this test case looks like is has the precedence.

Second Test Case#

I then created a second test case just changing around a little bit:

False is True in [False, True]

Running this through the same tests as above results in contradictory results!

>>> False is True in [False, True]
False
>>> (False is True) in [False, True]
True
>>> False is (True in [False, True])
False

Based on this test case, the in operator appears to be taking precedence.

Realization of what is and in are.#

After doing some digging in the Python documentation, I found the comparisons section discussing built in types. The handy chart clearly identifies that is and is not are considered comparison operators.

From my iterable membership check blog post, we know that in is a membership check. But a small note at the bottom of that section states:

Two more operations with the same syntactic priority, in and not in, are supported by types that are iterableor implement the __contains__() method.

This means that for practical purposes, both is and in are evaluated as though they are comparisons.

Comparison Chaining#

In a different documentation page on expressions, there is yet another section about comparisons.

As an aside, this section does include a subsection on membership test operations. This seems to imply that in is a comparison, but yet the previously referenced comparison docs in built-ins says it is not a comparison operator. As such, I am not 100% sure what the correct description of inshould be so my terminology my be a little bit fluid.

The biggest difference that this section points out, is that unlike C, all comparisons are lower priority than mathy operations, and that it is evaluated under mathematical conventions. This means that a < b < c will evaluate that a < b and b < c. Also, if a, b, or c is something like 1+2, it will evaluate 1+2 to be 3, then do the comparisons. Specifically it states:

Formally, if abc, …, yz are expressions and op1op2, …, opN are comparison operators, then a op1 b op2c ... y opN z is equivalent to a op1 b and b op2 c and ... y opN z, except that each expression is evaluated at most once.

I think my next post will have to dive into the “evaluated at most once” as this opens up some more fascinating edge cases…

The TL;DR is that comparisons chain, and do not have an order of operations or precedence over each other, because of this, the above test cases above need to be looked at again under this new light.

Reevaluating Test Cases#

With this newfound knowledge, lets look at the two test cases from above.

The generalized form we had was:

value_1 is value_2 in [list_values]

Knowing that is and in are comparisons and chaining applies, this can then be rewritten as:

value 1 is value_2 and value_2 in [list_values]

Therefore, our two test cases now look like:

False is False and False in [False, True]
False is True and True [False, True]

The second statements are True in both cases, since the list contains both False and True but first statements do vary. Since False and True are built-in definitions, they will pass the identity equality test of False is False or True is True. Understanding this, we can now see why the first statement evaluates to True, while the second evaluates to False, appearing to show a difference in whether is or in was evaluated first. The truth is that changing leads to both being evaluated in separate comparisons and then ensuring both are True.

Some Other Interesting Results#

This does mean that several other strange statements are valid.

a < b > c indicates that b is both greater than a and c, but makes no comparison between a and c.

a == b is a compares b to a with both value and identity comparisons, which may be an interesting check for things like math.nan from my previous blog posts on infinity and nan.

Or you could check that a == b is not a to check they have the same value but are not the same object.

Conclusions#

The most important lessons are that is and in are handled with the same priority as the typical comparison operators like == and > and that comparisons chain. Overall, this does make statements like a > b > c make sense to mathematically minded individuals, but when combined with is and in can yield some unpredictable results.

Generally, I would use chained operators when it clearly matches common mathematical expressions, but would break these chained operators apart for more esoteric examples to maintain readability.

I also quickly put this into some scripts in examples.py on GitHub as a starting place to do your own experimentation.