
Why does `True == False is False` evaluate to False in Python? (2013) - 2arrs2ells
http://stackoverflow.com/questions/17202207/why-does-true-false-is-false-evaluate-to-false
======
stinos
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.

~~~
userbinator
On 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
That 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
Just 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.

~~~
apricot
I sometimes teach programming to beginners who have some knowledge of math. A
common mistake they make is to write compound conditionals like this:

    
    
        if a == 2 or 3:
            do stuff
    

because that's how they would write it in math. Does that mean Python is
broken and should support that construct as well?

~~~
edflsafoiewq
I believe COBOL supports this.

------
BurningFrog
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?

~~~
Non24Throw
I think in language design there’s an expectation that if an expression or
statement doesn’t make any sense, then people won’t write it that way.

I think that’s a pretty reasonable expectation, too.

In JavaScript, {} + [] evaluates to integer 0. That doesn’t make any sense,
but it makes more sense after reading the ES spec for the addition operator.

There are many expressions you can write in dynamically typed languages that
don’t make any sense, probably most of them actually, but they have to be
considered valid because it’s a dynamically typed language. So they’re valid,
they will evaluate to something.

The language designers aren’t so concerned with identifying every possible
combination that makes no human-intuitive sense. The important part is that
when it seems like types should be inferred and coerced in a particular way,
then that’s how it should work. It should match human intuition.

I don’t have any intuition or opinion about how True == False is False should
be evaluated, this kind of thing is going to receive superfluous parentheses
from me every time for the benefit of the reader, and if someone else wrote it
this way I’m always going to look it up or test it in a REPL...

10 < x <= 100 though, if that’s considered a valid expression and it doesn’t
evaluate to true for numbers in the range (10,100], I’m going to stop using
that language...

~~~
simias
>In JavaScript, {} + [] evaluates to integer 0. That doesn’t make any sense,
but it makes more sense after reading the ES spec for the addition operator.

It's the consequence of ill-thought mechanics when it comes to type coercion.
It's the original sin of many scripting languages: "let's just add a bunch of
shortcuts everywhere so that it doesn't get in the way of the developer trying
to make a quick script". Then you end up with a bunch of ad-hoc type coercion
and the language guessing what the user means all over the place, and
eventually these bespoke rules interact with each other in weird ways and you
end up with "{} + [] = 0".

> I think in language design there’s an expectation that if an expression or
> statement doesn’t make any sense, then people won’t write it that way.

That's either very idealistic or very naive. In either case I'd argue that's a
terrible way to approach language design. I'd argue that many well designed
languages don't make any such assumptions.

>but they have to be considered valid because it’s a dynamically typed
language.

Nonsense. Try typing `{} + []` in a python REPL. You seem to be suffering from
some sort of Javascript-induced Stockholm syndrome, or maybe simply lack of
experience in other languages. JS does the thing it does because it was
designed(?) that way, not because there's some fundamental rule that says that
dynamically typed languages should just do "whatever lol" when evaluating an
expression.

~~~
Non24Throw
I don’t write JavaScript or have a strong opinion about it.

The widespread prevalence of JS transpilers and everyone’s apparent
unwillingness to write pure JS makes me think it probably isn’t such a great
language. It probably never had a chance given its history with browsers.

Just using it to make the point that every language has valid expressions that
make no sense. You can find similar examples in every language. All you have
to do to find them is start writing code in a way that no person ever would or
should write it.

Especially in the case of dynamically typed languages, throwing an exception
sometimes but not always based on the “human intuitiveness” of any given type
inference would make the language even more unpredictable. It’s just instead
of asking why expression a evaluates to x, we would all be asking why
expression a evaluates to x but similar expression b throws an exception.

If you ask me, the latter is even more arbitrary.

These aren’t useful criticisms. The only reason these kind of critiques even
get so much attention is because people reading the headline think: “I wonder
how the hell that would be evaluated?” And so they click.

The headline is only interesting to begin with because nobody ever writes
that, and nobody should ever write that.

If nobody would ever write it or have any expectations about its evaluation,
then how is it even significant that the language will interpret it one way vs
another?

I think these criticisms are a good springboard to have the debate about
static vs dynamic typing, but arguing over whether True == False is False
should be evaluated one way vs another is kind of pointless. If the result of
that argument is agreement, then we might end up with people actually writing
this, which should be the last thing any of us want.

------
Figs
If anyone's looking for some more good WTFs in Python:
[https://github.com/satwikkansal/wtfpython](https://github.com/satwikkansal/wtfpython)

~~~
nickm12
I must be a crusty Python programmer, because none of the first few examples
seemed like WTFs to me at all. I think a WTF is when you think you know a
language and then see something that behaves completely unlike how you expect.

~~~
ben509
I was annoyed by the number of RTFMs they call WTFs. They even claim some WTFs
where Python behaves exactly as a naive user would expect, e.g. if I put 5 and
5.0 in a dict they are treated as the same key.

I saw two valid ones: "Lossy zip of iterators" is because Python conflates
iterables and iterators, "The disappearing variable from outer scope" where
deleting the exception is contrary to how scope works everywhere else.

They also miss the classic newbie head-scratcher:

    
    
        x = []
        for i in range(10):
            x.append(lambda: i)
        x[0]()

------
userbinator
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.

~~~
seisvelas
Yes, 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-a...](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-
abstractions/)

~~~
userbinator
For 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
But also, those high ladders of abstraction support some pretty cool
software...

------
fhars
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.”

------
m12k
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.

~~~
throwaway_pdp09
I 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.

~~~
jaggirs
Negative 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.

~~~
throwaway_pdp09
I know python. I have rarely found negative indices useful except to pick out
the last item of a list, and I've done a decent amount of it, though no
numpy/pandas yet. In what way is negative indices particularly useful for
these latter two?

I have no problem with slicing. You can't get it accidentally wrong because
the syntax is visible. Negative indexing vs positive indexing can't generally
be distinguished just by looking at the indexing statement, which is where I
tripped up.

------
nickcw
I 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 :-)

------
jgoodknight
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!

------
bnegreve
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!

~~~
weare138
If 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
That wouldn't work for the even parity case.

"(x in odds) == expect_odd" gives the intended behaviour and I think is easier
to read as well.

------
koliber
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.

------
lifthrasiir
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...](https://docs.python.org/3.8/reference/expressions.html#operator-
precedence)

------
6gvONxR4sf7o
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.

------
bannatech
I wrote up a post on this same kind of expression:
[https://banna.tech/post/chained_conditional_expressions_in_p...](https://banna.tech/post/chained_conditional_expressions_in_python/)

------
jasonpeacock
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.

~~~
raylu
Don'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.

------
forumranger

      >>> True is (False is False)
      True
      >>> True == (False is False)
      True

------
thisisyuu
Someone writing code like these, his life is sad.

~~~
raylu
IIRC, 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.

------
breatheoften
does ruby do anything like this? i'm new to ruby and could imagine something
like this biting me ...

~~~
mpd
Yes. 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
I 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)

------
njharman
Explicit is better than implicit.

    
    
       >>> (True == False) is False
       True

------
raylu
Oh, hi!

Yeah, that was a real head-scratcher.

------
choward
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".

------
kingname
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.

------
echelon
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.

~~~
1337shadow
You'll need to find another one, the above results in SyntaxError: cannot
assign to True

~~~
echelon
I 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-t...](https://stackoverflow.com/questions/13665989/in-python-how-to-
recover-from-joke-statements-like-true-false)

------
Animats
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](http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node74.html)

~~~
matheusmoreira
So all Python numbers are objects and the implementation automatically pre-
allocates and reuses instances for small numbers? What a strange
implementation. I thought it used tagged pointers like other dynamic
languages. In Ruby, values can be pointers to objects but also 63 bit numbers
when the least significant bit is set.

[https://en.wikipedia.org/wiki/Tagged_pointer](https://en.wikipedia.org/wiki/Tagged_pointer)

This issue of object identity and equality isn't unique to Lisp but I agree
that applying it to numbers produces unexpected results.

~~~
pansa2
> I thought it used tagged pointers like other dynamic languages.

No. IIRC tagged pointers were considered too complex to expose via Python’s C
API.

~~~
__s
[https://mail.python.org/pipermail/python-
dev/2004-July/04613...](https://mail.python.org/pipermail/python-
dev/2004-July/046139.html)

-1000 from GvR

~~~
userbinator
Here's his actual reply: [https://mail.python.org/pipermail/python-
dev/2004-July/04614...](https://mail.python.org/pipermail/python-
dev/2004-July/046147.html)

