156

When I was looking at answers to this question, I found I didn't understand my own answer.

I don't really understand how this is being parsed. Why does the second example return False?

>>> 1 in [1,0]             # This is expected
True
>>> 1 in [1,0] == True     # This is strange
False
>>> (1 in [1,0]) == True   # This is what I wanted it to be
True
>>> 1 in ([1,0] == True)   # But it's not just a precedence issue!
                           # It did not raise an exception on the second example.

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    1 in ([1,0] == True)
TypeError: argument of type 'bool' is not iterable

Thanks for any help. I think I must be missing something really obvious.


I think this is subtly different to the linked duplicate:

Why does the expression 0 < 0 == 0 return False in Python?.

Both questions are to do with human comprehension of the expression. There seemed to be two ways (to my mind) of evaluating the expression. Of course neither were correct, but in my example, the last interpretation is impossible.

Looking at 0 < 0 == 0 you could imagine each half being evaluated and making sense as an expression:

>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True

So the link answers why this evaluates False:

>>> 0 < 0 == 0
False

But with my example 1 in ([1,0] == True) doesn't make sense as an expression, so instead of there being two (admittedly wrong) possible interpretations, only one seems possible:

>>> (1 in [1,0]) == True
Community
  • 1
  • 1
Peter Wood
  • 23,859
  • 5
  • 60
  • 99

1 Answers1

201

Python actually applies comparison operator chaining here. The expression is translated to

(1 in [1, 0]) and ([1, 0] == True)

which is obviously False.

This also happens for expressions like

a < b < c

which translate to

(a < b) and (b < c)

(without evaluating b twice).

See the Python language documentation for further details.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 40
    Additional proof for this, `1 in [1, 0] == [1, 0]` evaluates to `True`. – Andrew Clark Feb 14 '12 at 21:29
  • 10
    I've long thought of this as a language wart. I would have preferred that the `in` operator have a higher precedence than other comparison operators and that it not chain. But perhaps I'm missing a use case. – Steven Rumbalski Feb 14 '12 at 21:54
  • 4
    nice catch, I didn't even think of that. It doesn't make much sense to allow chaining of `in` - after all `x < y < z` makes sense, but not so much with `x in y in z` – BlueRaja - Danny Pflughoeft Feb 14 '12 at 22:00
  • 1
    @StevenRumbalski: Something like `0 <= f() in my_set` *might* sometimes be useful since the alternative `0 <= f() and f() in my_set` calls `f()` twice. I'd probably use a variable for this, though. – Sven Marnach Feb 14 '12 at 22:05
  • 7
    @Sven Useful: maybe. Readable: definitely not. Python purports to emulate common mathematical typography with this convention, but when used with `in` this is simply no longer the case and makes it quite counter-intuitive. – Konrad Rudolph Feb 15 '12 at 00:42
  • 7
    @KonradRudolph: I've seen thinks like "1 ≤ x ∈ ℝ" in mathematical texts more than once, but I basically agree with you. – Sven Marnach Feb 15 '12 at 01:00
  • 1
    @StevenRumbalski, There have been many times when I've wanted to say `1 < x < 10` and the like in other languages; comparison chaining is quite a convenient feature in those sorts of situations. People who haven't learned other programming languages will understand this behavior naturally. The main issue is that implementing this feature becomes ambiguous for situations where you wouldn't normally use comparison chaining (such as in this question). The primary issue being that too many operations are happening in an illegible manner, and the code needs to be written to be more explicit. – zzzzBov Feb 15 '12 at 02:38
  • 1
    @zzzBov: it sounds like you are agreeing with me. The only operator I want exempted from chaining is the 'in' operator. I'm otherwise pleased with the status quo. – Steven Rumbalski Feb 15 '12 at 03:39
  • @SvenMarnach How did you find this out? – pkoch Feb 16 '12 at 17:42
  • 2
    @pkoch: I'm rather familiar with the Python language and knew about comparison operator chaining. It's not too much of a secret -- even the [tutorial explains it](http://docs.python.org/tutorial/datastructures.html#more-on-conditions). All I had to do is double check that it also applies to the `in` operator, but this was actually clear from the question. See also the link in my post. – Sven Marnach Feb 16 '12 at 19:24
  • 2
    @SvenMarnach I knew about chaining, it just wasn't that obvious. `in` didn't mentally match as a comparison operator. However, searching for your magical ways of groking python, I came across module ast. And it does shed some light. Try `ast.dump(ast.parse('1 in [0,1] == True').body[0])` and `ast.dump(ast.parse('(1 in [0,1]) == True').body[0])`. – pkoch Feb 17 '12 at 19:16
  • as you said `(a < b) and (b < c)` translates for `a < b < c`. What happens if statement is like `a < b < c < d` @SvenMarnach – Sanket Feb 23 '18 at 09:27
  • @Sanket The obvious thing – it is equivalent to `(a < b) and (b < c) and (c < d)`, but with `b` and `c` only evaluated once. Since the logical `and` is associative, we don't need to worry about further parentheses. – Sven Marnach Feb 23 '18 at 10:54
  • This is why I consider `== True` to be an anti-pattern and avoid it in all my code. – Mark Ransom May 02 '20 at 04:19
  • @MarkRansom `== True` is an anitpattern in _any_ language, since it's redundant, and adds unnecessary noise. Granted, this pathological case makes it particularly bad in Python. – Sven Marnach May 02 '20 at 21:01
  • @SvenMarnach you're right, ugly and redundant is reason enough to avoid it. Causing bugs is just more fuel on the fire. – Mark Ransom May 02 '20 at 23:35