Chained comparison in Python

George Shuklin
OpsOps
Published in
2 min readAug 22, 2020

There was a ‘funny WTF’ I found:

>>> 3 is 3 is True
False

and

>>> True is 3 is 3
False

It looked like obscure “you don’t remember evaluation order for the ‘is’ operator”, because:

>>> (True is 3) is 3
False
>>> True is (3 is 3)
True

Oh, problem solved. Is it? How abut case1? Or this?

>>> 3 is 3 is 3
True

If it was just braces, so why this?

>>> (3 is 3) is 3
False

Here is one more hint before I reveal why:

>>> 3==3==3
True
>>> (3==3)==3
False

To my remorse I hadn’t dig this out. My friend from LJ did (Russian).

Chained comparison

Let me introduce the feature causing this kind of behavior. It called ‘chained comparison’ and it’s in the docs. Unfortunately, it’s too deep on details (like single/double evaluation) and the main ‘and’ is not on the banner.

Digression: When I learned Python (after few years of C) I was astonished by simplicity of if x < y < z compare to cumbersome C-style (if( x<y and y < z)). It was natural and easy to read, so I accepted it as it is without digging deeper. Not like I was able to dig deeper into Python at that moment. Later, when I have been able to dig deeper, it ingrained too deep already. It was like that until I got this ‘python puzzle’, which I was motivated enough to post, but not enough to dissect by myself.

Every time Python see chained comparison (=, <, >, is, in), it does something unusual. It converts elegant Python expression into that old cumbersome C-style expression.

3 is 3 is 3 actually, is (3 is 3) and (3 is 3). It happened with every ‘truth-revealing’ operator. Mystery solved.

Until it not.

When I wrote a list of operators for chaining, I checked if ‘in’ supported it or not. It is supported, but in the form I hardly accept as reasonable.

Chained 'in'

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

Yes, ‘in’ can be chained too, but the semantic is more than vague here. Given the half-screen of ‘chained’, ‘chained’, ‘chained’, we kinda can guess this example is as it is.

But if you see this in a production code:

if name in user_names in allowed_names:
yield Success(name)

Can you guess what’s going on? The semantic here is super misleading, and language is really helping to make it as misleading as possible. Additionally, this code going to work somehow but totally of a different reason.

Chained comparison for ‘<’, ‘=’ and ‘>’ is really cool, ‘is’ slightly fishy, and ‘in’ is really in WTF group.

I put this into my ‘bad Python’ bag to brag at geek parties.

--

--

George Shuklin
OpsOps

I work at Servers.com, most of my stories are about Ansible, Ceph, Python, Openstack and Linux. My hobby is Rust.