from Hacker News

Why does `True == False is False` evaluate to False in Python? (2013)

by 2arrs2ells on 5/2/20, 2:23 AM with 217 comments

  • by stinos on 5/2/20, 6:24 AM

    One of the comments lays out why I probably never use things like this: 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

    Apart from unexpected behavior it is also about readability: with parentheses in place you can just read left-to-right and the parsing overhead is minimal. Without them though, you have to read almost everything first, parse what is there, figure out what comes first (and hope you remembred it correctly), then read again to get a mental image of what is actually going on.

  • by BurningFrog on 5/2/20, 3:19 AM

    I like chained comparisons for the `10 < x <= 100`, since it makes intuitive sense and removes duplication.

    But I can't think of any case with `==` type operators, or really any other operators where it also makes sense.

    So was that maybe an overgeneralized feature that should have been limited to the math operators?

  • by Figs on 5/2/20, 3:51 AM

    If anyone's looking for some more good WTFs in Python: https://github.com/satwikkansal/wtfpython
  • by userbinator on 5/2/20, 4:40 AM

    This seems to be another instance of the general situation where trying to "helpful" by introducing a special-case rule (comparison chaining) that is intended to make some uses more convenient, also introduces perplexing behaviour for other cases. I'm far more accustomed to the C-family languages, where parsing is usually quite uniform, so to me "these operators are binary and left-associative like all the others, except when more than one occur in an expression" seems like a trap. I wonder if the short-circuiting behaviour of the implicit && has also caused some surprises, even in the x < y < z() type of expressions that this feature was intended for.
  • by fhars on 5/2/20, 9:03 AM

    I can‘t help but read most of the moaning about Python‘s handling of chained comparisons in this thread as “I already know how Blubb handles comparisons, thank you very much, and if Python doesn’t do it exactly the same way Blubb does, Python is obviously stupid and it’s designers must be morons.”
  • by m12k on 5/2/20, 8:55 AM

    I honestly don't think being able to write 'a < b < c' is worth making the language bigger and causing weirdness and gotchas like this. 'a < b && b < c' isn't that much longer and is instantly and unambiguously readable to programmers coming from hundreds of other languages.
  • by nickcw on 5/2/20, 10:26 AM

    I tried this in gpython ( https://github.com/go-python/gpython ) and it works.

    That isn't surprising I suppose however what is surprising is that I wrote gpython and I had no idea why it worked until I read the explanation on stack overflow about 5 times.

    I guess that is the power of having implementing the grammar.

    I always like it when my creations (programs or children) exceed me :-)

  • by jgoodknight on 5/2/20, 5:36 AM

    I find it cool to explore these edge cases, but putting anything like this in a real code base is a terrible idea BECAUSE there are so many different ways to interpret it. Sure a < b <= c has a clear mathematical meaning which works towards python's overall mission of being clear, but in general please good people only have one or two variables in your conditional statements!
  • by bnegreve on 5/2/20, 4:53 AM

    I find something like this totally plausible and yet it is completely wrong.

        >>> def check_parity(x, expect_odd):
        ...     odds = [ 1, 3, 5 ]
        ...     if x in odds == expect_odd :
        ...             print("ok")
        ...     else:
        ...             print("error")
        ... 
        >>> check_parity(5, True)
        error
    
    Crazy!
  • by koliber on 5/2/20, 11:40 AM

    This is neat. My first reaction was confusion and a bit of shock. But then it made sense. And it makes a lot of sense.

    Part of the issue is that “==“ and “is” are intermixed. That emphasizes the weirdness but detracts from understanding the underlying mechanism that is at work.

    If you look at

    True == False == False

    It makes more a bit sense that it evaluates the way it does.

    If you do

    1 == 2 == 2

    and it evaluates to False, then it is perfectly clear.

  • by lifthrasiir on 5/2/20, 11:44 AM

    There are many kinds of chained operators (in some languages even associative arithmetic operators are chained). Contrary to popular beliefs, this is nothing to do with chained operators but rather with operator precedences.

    It is pretty common that arithmetic comparison operators are grouped to a single precedence level and that's not a problem. But in Python `is`, `is not`, `in` and `not in` are also in that level. In particular two operands of `in` and `not in` have different [1] types unlike others. Mixing them are, either with or without chained operators, almost surely incorrect.

    This kind of precedence issue can be solved by introducing non-associative pairs of operators (or precedence levels), something that---unfortunately---I don't see much in common programming languages. Ideally Python's operator precedence table should look like this (compare with the current documentation [2]):

        Operator                                Description
        --------------------------------------  ------------------------
        ...                                     ...
        
        `not x`                                 Boolean NOT
         _______________________________________________________________
        |
        | The following groups do not mix to each other.
        | Use parentheses to clarify what you mean.
        | ______________________________________________________________
        ||
        || `in`, `not in`                       Membership tests
        ||
        || `is`, `is not`                       Identity tests
        ||
        || `<`, `<=`, `>`, `>=`, `!=`, `==`     Comparisons
        ||______________________________________________________________
        |_______________________________________________________________
        
        `|`                                     Bitwise OR
        
        ...                                     ...
    
    In fact, there is already one non-associative pair in Python: `not` and virtually every operator except boolean operators. It is understandable: the inability to parse `3 + not 4` is marginal but you don't want `3 is not 4` to be parsed as `3 is (not 4)`. My point is that, if we already have such a pair why can't we have more?

    [1] With an exception of strings (`"a" in "abcdef"`). I hate that Python doesn't have a character type.

    [2] https://docs.python.org/3.8/reference/expressions.html#opera...

  • by 6gvONxR4sf7o on 5/2/20, 6:18 AM

    It seems like only operators with a nice transitivity should be supported. x < y < z. x == y == z. x is y is z. That kind of thing. x != y != z doesn't work because in normal language, you'd say that to mean that they're all unique, while allowing it the python way, it doesn't imply x != z.
  • by bannatech on 5/2/20, 3:36 AM

    I wrote up a post on this same kind of expression: https://banna.tech/post/chained_conditional_expressions_in_p...
  • by jasonpeacock on 5/2/20, 2:56 PM

    You're mixing comparison operators (`==` and `is`), which is a code smell.

    It doesn't matter what the result is - you know it's going to bite you eventually. If you run a linter on this it would correctly yell at you.

  • by thisisyuu on 5/2/20, 3:48 PM

    Someone writing code like these, his life is sad.
  • by breatheoften on 5/2/20, 3:00 AM

    does ruby do anything like this? i'm new to ruby and could imagine something like this biting me ...
  • by forumranger on 5/5/20, 7:46 PM

      >>> True is (False is False)
      True
      >>> True == (False is False)
      True
  • by njharman on 5/2/20, 10:25 AM

    Explicit is better than implicit.

       >>> (True == False) is False
       True
  • by raylu on 5/2/20, 5:58 AM

    Oh, hi!

    Yeah, that was a real head-scratcher.

  • by choward on 5/2/20, 3:41 PM

    170 comments here so far because of some syntactic sugar. If it causes this much discussion and confusion it's not worth it IMO. I'm glad none of the languages I use have this "feature".
  • by kingname on 5/6/20, 3:49 AM

    tl,dr: if you know why does the result of `1 < 2 < 3` is True,then,you know why `True == False is False` is False. Forget other language, first. In Python the chain compare means `1 < 2 < 3` means `1 < 2 and 2 < 3`, so `True == False is False` means `True == False and False is False` and equal to `False and True`. So, the result is False.
  • by echelon on 5/2/20, 9:15 AM

    This can be fixed by one of my favorite Python oddities,

    True = False

    This is right up there with default arg instances getting cached across calls, though it's perhaps better suited for an underhanded Python competition.

    Have fun with it. Redefine it to be true 90% of the time.

  • by Animats on 5/2/20, 3:45 AM

    It's just a operator precedence problem. Add parentheses and it goes away.

        Python 3.6.9 (default, Apr 18 2020, 01:56:04) 
        >>> True == False is False
        False
        >>> (True == False) is False
        True
    
    There are worse problems with Python's "is".

        >>> 1+1 is 2
        True
        >>> 1000+1000 is 2000
        False
    
    This comes from a bad idea borrowed from LISP. Numbers are boxed, and the small integers have boxes built in for them. Larger numbers have boxes dynamically generated. In Python "is" means "in the same box". This corresponds to (eq a b) in LISP.[1] Exposing the implementation like that might have been a good idea when McCarthy came up with it in 1960.

    [1] http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node74.html