110

I get some rather unexpected behavior on an expression that works with == but not with is:

>>> (True == False) is False
True
>>> True == (False is False)
True
>>> True == False is False
False
>>> id(True)
8978640
>>> id(False)
8978192
>>> id(True == False)
8978192
>>> id(False is False)
8978640
raylu
  • 2,630
  • 3
  • 17
  • 23
  • 1
    Oh. Turns out it had nothing to do with `is` vs `==`, as the expression evaluates to `False` in either case. Thanks for all the quick answers! – raylu Jun 19 '13 at 22:22
  • @MartijnPieters While typing my answer I thought this must have been asked before, but I guess it can be difficult to google. This [question](http://stackoverflow.com/questions/9284350/why-does-1-in-1-0-true-evaluate-to-false?) could be another duplicate. – jorgeca Jun 19 '13 at 22:41
  • Related to my question: [Why does (1 in [1,0\] == True) evaluate to False?](http://stackoverflow.com/questions/9284350/why-does-1-in-1-0-true-evaluate-to-false) – Peter Wood Jun 19 '13 at 23:06
  • 2
    I think this would make an excellent interview question for someone who answers "10" to the question "on a scale of 1 to 10, how are your python skills"? – Bryan Oakley Jun 20 '13 at 13:40
  • Why should this evaluate to True ? Absolutely no reason: `True is False` evaluates to False, so does `True == False`, why should `True == False is False` evaluate to True then ? – jpic May 02 '20 at 19:13
  • 1
    @jpic: The spirit of the question, I believe, is that many people will write True == False is False thinking that it will be parsed as (True == False) is False, which evaluates to FALSE is False, and therefore should be True. – roberto May 03 '20 at 11:59

4 Answers4

188

Because in fact that's a chained comparison, so

True == False is False

is equivalent to

(True == False) and (False is False)

This can be surprising in this case, but lets you write 1 <= x < 4 unlike in other languages like C.

jorgeca
  • 5,482
  • 3
  • 24
  • 36
  • 1
    (The parenthesis are [never](http://docs.python.org/2/reference/expressions.html#operator-precedence) needed, by the way) – jorgeca Jun 19 '13 at 22:15
  • 1
    Obviously that's only if they have the same precedence – NullUserException Jun 19 '13 at 22:17
  • @NullUserException Mmm I think they can't be needed because the possible operators are `"<" | ">" | "==" | ">=" | "<=" | "<>" | "!=" | "is" ["not"] | ["not"] "in"` which all have less precedence than `and`. (By the way, thanks for the grammar fix, that really helps us non native speakers). – jorgeca Jun 19 '13 at 22:19
  • 38
    And this is exactly why I tend to wrap compound logical comparisons in parens, I simply can't remember the syntax across all the languages I use, and it's not worth the risk of leaving them off and having unexpected behavior. – zzzzBov Jun 19 '13 at 22:40
  • 7
    @zzzzBov I fully agree, that's why I *did* wrap them ;) – jorgeca Jun 19 '13 at 22:43
  • How do you explain this? `>>> bool(True == False) is False` `True` – CallMarl May 02 '20 at 10:32
  • @CallMarl `bool(True == False)` evaluates to `False` and since `False` is `False`, the entire expression evaluates to `True` – Michael Mior May 02 '20 at 12:16
37

From the docs:

x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

In your case True == False is False is equivalent to True == False and False is False as the first condition is False so it short-circuits and return False.

>>> dis.dis(lambda : True == False is False)
  1           0 LOAD_GLOBAL              0 (True)
              3 LOAD_GLOBAL              1 (False)
              6 DUP_TOP             
              7 ROT_THREE           
              8 COMPARE_OP               2 (==)
             11 JUMP_IF_FALSE_OR_POP    21          <---------this step
             14 LOAD_GLOBAL              1 (False)
             17 COMPARE_OP               8 (is)
             20 RETURN_VALUE        
        >>   21 ROT_TWO             
             22 POP_TOP             
             23 RETURN_VALUE  
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • 9
    thanks for the dis.dis(). I learned something useful.:) – pymd Jun 19 '13 at 22:22
  • 2
    I didn't downvote, but it seems to me that the description of the language's rules is quite enough to understand what's going on. The disassembly doesn't seem to add much (other than a focus on implementation-specific details). – cHao Jun 20 '13 at 17:55
9

From the documentation:

5.9. Comparisons

Unlike C, all comparison operations in Python have the same priority, which is lower than that of any arithmetic, shifting or bitwise operation. Also unlike C, expressions like a < b < c have the interpretation that is conventional in mathematics:

comparison    ::=  or_expr ( comp_operator or_expr )*
comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "<>" | "!="
                   | "is" ["not"] | ["not"] "in"
SylvainD
  • 1,743
  • 1
  • 11
  • 27
4

True == False is False is a chained comparison, which means the same as (True == False) and (False is False). Since the first comparison (True==False) is false, the result of the chained comparison is False.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384