20 ms·
Why does `True == False is False` evaluate to False in Python? (2013)
- breatheoften 6y agodoes ruby do anything like this? i'm new to ruby and could imagine something like this biting me ...
- mpd 6y agoYes. Check out the differences between `&&` vs. `and`, along with `or` vs. `||`, which can lead to similar surprises. Though you can skip this lesson if you've worked with Perl (any others?) in the past.
- willbutler 6y agoI agree with the folks at Airbnb regarding the and, or, and not keywords. "It's just not worth it." [1]. [1] https://github.com/airbnb/ruby#no-and-or https://github.com/airbnb/ruby#no-and-or
- jes5199 6y agonot really. there’s some weird operator precedence, but it doesn’t have any multi-operator expressions
- waffle_ss 6y agoOne weird operator that comes to mind is the flip-flop operator, but the odds of encountering it are close to zero, and it would certainly stick out as something very bizarre and not confused with other syntax. https://chrisseaton.com/truffleruby/flip-flops/ https://chrisseaton.com/truffleruby/flip-flops/
- jes5199 6y ago!?! I’ve been working in ruby for 15 years and I had never heard if the flip flop operator! Amazing. Thanks for the counter-example, I have learned something
- BurningFrog 6y agoI 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?
- klodolph 6y agoI can think of a few cases where I want: if x == y == z:
- nogridbag 6y agoI always found prefix notation in Clojure to be really elegant for a lot these cases that are odd in other languages: (= x y z)
- ramshorns 6y agoThis is a bit like something you can do in C, almost by accident as a bonus language feature. x = y = z = 4; But I think this is assignment expressions which were controversial in Python or something.
- mark-r 6y agoNot anymore, check out the walrus operator.
- sk0g 6y agoYeah in Python you can do, say, a = b = [] But then the two lists are the same, and appending to one appends to the other. Not particularly useful.
- Ragib_Zaman 6y agoIt's useful when the object being assigned to those variables are literals instead of reference types, which in my experience is most of the time when you want to do the simultaneous assignment. When you do want to do reference types, you can do a,b = [], [].
- bannatech 6y agoI wrote up a post on this same kind of expression: https://banna.tech/post/chained_conditional_expressions_in_python/ https://banna.tech/post/chained_conditional_expressions_in_p...
- Animats 6y agoIt'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 http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node74.html
- hombre_fatal 6y agoIt's not demonstrating an operator precedence problem because you still aren't explaining why `True == False is False` returns False and that was the question. In fact, a problem is that it looks like an operator precedence issue at first glance. Of course, TFA explains the answer.
- bostonpete 6y agoHow is that operator precedence? Putting the parens around "False is False" also makes the expression True. The reason (chained comparison) is explained in the link and I don't think it amounts to operator precedence....(?)
- gpm 6y agoIt's not operator precedence, both (True == False) is False and True == (False is False) are true. As explained in the link, it's because of chained comparisons, it expands to True == False and False is False. More useful for expressions like 1 < x < 3.
- kazinator 6y agoI don't think that different equalities like "==" and "is" should be chained together; that is completely wrong. If it must be done for consistency in syntax/parsing, then such combinations should be semantically diagnosed and rejected. "x is y is z" -> good. "x == y == z" -> good. "x == y is z" -> WTF, error. All the operators in the "relational cluster" should belong to the same equivalence family.
- Figs 6y agoIf anyone's looking for some more good WTFs in Python: https://github.com/satwikkansal/wtfpython https://github.com/satwikkansal/wtfpython
- maxwelljoslyn 6y agoWow. (a) I don't know Python as well as I thought I did. (b) I suddenly never want to use it again. All these edge cases! All these behaviors, which I'm sure were added with the noble intention of increasing developer convenience, but which I'm equally sure have cost a larger amount of developer sanity!
- tziki 6y agoIf you don't want to use a language because in theoretical scenarios it's possible construct unintuitive operations with it, then what language are you left with?
- afandian 6y agoLISP?
- tromp 6y agoI was going to bring up Haskell as a language with clear semantics, but even that has its quirks, like Prelude> [1, 3 .. 10] :: [Float] [1.0,3.0,5.0,7.0,9.0,11.0] In Haskell, this is syntactic sugar for the function enumFromThenTo (in typeclass Enum), which in my view should not have a special case implementation for Float.
- tyre 6y agoI've been writing python for about a year and change now (~7 years of engineering generally) and I never want to use it again. It feels to me like javascript in that it's "popular" because people already use/know it. So that huge existing codebase is the equivalent to the web; if you want to build on it, you're stuck with this. But my lord. Whitespace sensitivity is a terrible choice and there are piles of kludges trying to work around that. It means no multi-line lambdas so you end up with these unreadable list comprehensions `[ sub_item.value for sub_item in item.sub_items in items if item.is_the_best ]`. Lines copied into the console care about indentation which is definitely not a fun DX. Not to mention these random global functions everywhere. Whew. Mistakes were made.
- userbinator 6y agoThis 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.
- seisvelas 6y agoYes, I was going to comment much the same but with the addition that Joel Spolsky famously documented this phenomenon is his essay on The Law of Leaky Abstractions: https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/ https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...
- userbinator 6y agoFor an 18-year-old article, it sure is prescient --- the state of modern software seems to be all about gluing together numerous layers of abstraction and libraries without understanding them, with the result that whenever something goes wrong, as it inevitably will, it takes even longer to diagnose. The higher you are on the ladder of abstraction, the worse the fall.
- raylu 6y agoBut also, those high ladders of abstraction support some pretty cool software...
- bnegreve 6y agoI 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!
- skrebbel 6y agoCan you explain why it doesn't work?
- tryptophan 6y agoIt's: if (x in odds} and (odds == expect_odd). The 2nd expression is obviously false always.
- wadkar 6y agoBecause chaining the operator results in: > if x in odds == expect_odd : > if x in odds and odds == expect_odd : with the latter always evaluating to False (assuming expect_odd is a boolean flag).
- weare138 6y agoI'm not a Python expert so I don't know much about the internals of Python but my hunch is when Python evaluates 'x in odds' it's not storing that value internally as a boolean so when you use the '==' strict comparison Python is expecting both data types to be the same.
- weare138 6y agoIf I understand what you're getting at changing '==' to 'and' will make it work the way you're expecting. Both have to evaluate to 'True' in the if statement and passing 'False' to expect_odd will always print 'error'.
- bacon_waffle 6y ago
- jgoodknight 6y agoI 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!
- raylu 6y agoOh, hi! Yeah, that was a real head-scratcher.
- deleted 6y ago[deleted]
- 6gvONxR4sf7o 6y agoIt 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.
- stinos 6y agoOne 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.
- userbinator 6y agoOn the other hand, Python is the only language I've used which has this special-case parsing. In all others, a < b < c would be parsed as ((a < b) < c) which may or may not be a type error, but it's consistent with the other binary operators.
- stinos 6y agoThat actually proves my point. I'm not really good in learning certain facts by heart so if I see a < b < c it is going to take multiple seconds, likely followed by an internet search so add a couple of minutes to that, before I can be 100% sure what it does. I mean, I know what precedence is and I'd probably assume it would be (a < b) < c but that's not going to cut it.
- aktuel 6y agoJust program in assembler then. Honestly, Python's way is the only sane way to parse this. This is how a human who never programmed before with some knowledge of math would understand it. Everyone's else mind is just corrupted by broken legacy languages.
- xyproto 6y agoMath notation is just one of many possible notations, though. And math notation is not static throughout history.
- m12k 6y agoI 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.
- throwaway_pdp09 6y agoI really agree with this. Maths <> programming, so while some notation sharing is good, don't (IMO) take it too far. I don't like it either when languages get too helpful. I was recently doing some python and getting some very odd results. Turned out the index I was using to pull stuff from the list had gone negative (off by one error) so it was getting stuff from the end of the list rather than excepting as most languages would. That is y = [1,2,3] y[-1] <-- unintentionally negative index I'm not saying the ability to index with negatives like this is a bad thing in python, but I seriously question it's value vs the footgun thing. A while ago I was doing some SQL and used an answer off stack overflow. It was a good answer but it tried to be too flipping helpful. IIRC it was doing nontrivial date difference calculations[0]. Rather 'helpfully' if "(date2 - date1) < 0" it would kindly assume you'd given it arguments in the wrong order and silently flip them for you (to "date1 - date2") to get you a positive number, always. This 'helpful' behaviour hid the presence of bad data (date2 should always have been chronologically later than date1 - date1 was interview, date2 was when job started). Moral: keep programming language & library semantics simple. [0] I remember now, it was the number of weekdays (that is, excl. sat/sun) between 2 dates.
- amedvednikov 6y agoYeah this is exactly why negative indexing is a bad idea and why I'm against introducing this in V.
- jaggirs 6y agoNegative indexing (along with the related syntax for slicing) is one of the most usefull python features. Numpy and pandas thrive on it. Just learn the language, python is already one of the easiest languages out there.
- fhars 6y agoI 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.”
- echelon 6y agoThis 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.
- 1337shadow 6y agoYou'll need to find another one, the above results in SyntaxError: cannot assign to True
- echelon 6y agoI could have sworn that was still a thing in Python 3, but it looks like they've formally been keywords since 3.0. I suppose it took awhile for the default Python shipped with systems to be 3.*, because I show people this anytime Gary Bernhardt's "wat" talk is brought up. Edit : Here's some of the fun from Python 2.X not treating True and False as keywords: https://stackoverflow.com/questions/13665989/in-python-how-to-recover-from-joke-statements-like-true-false https://stackoverflow.com/questions/13665989/in-python-how-t...
- njharman 6y ago> default arg instances getting cached across calls That is not what is happening at all, logically or semantically. Effectively this is. # People not understanding when the # function definition including arguments is evaluated. mutable_instance = list() def func(change_me=mutable_instance): pass
- pansa2 6y agoWouldn’t something like this have been better? def func(change_me=None): if change_me is None: change_me = list() ...
- njharman 6y agoExplicit is better than implicit. >>> (True == False) is False True
- nickcw 6y agoI tried this in gpython ( https://github.com/go-python/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 :-)
- koliber 6y agoThis 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.
- lifthrasiir 6y agoThere 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#operator-precedence https://docs.python.org/3.8/reference/expressions.html#opera...
- jasonpeacock 6y agoYou'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.
- raylu 6y agoDon't jump to conclusions too fast about a reduced example to demonstrate a problem. I probably originally had 2 expressions that evaluated to booleans. I may have been using `is` to check that the type of one was actually a bool rather than just falsey.
- choward 6y ago170 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".
- thisisyuu 6y agoSomeone writing code like these, his life is sad.
- raylu 6y agoIIRC, I originally had 2 expressions that evaluated to booleans that I was comparing. I don't remember if I had parens, but I do remember being deeply confused once there were no parens.
- deleted 6y ago[deleted]
- forumranger 6y ago>>> True is (False is False) True >>> True == (False is False) True
- kingname 6y agotl,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.