Python's in (__contains__) operator returns a bool whose value is neither True nor False
Asked Answered
A

1

33

As expected, 1 is not contained by the empty tuple

>>> 1 in ()
False

but the False value returned is not equal to False

>>> 1 in () == False
False

Looking at it another way, the in operator returns a bool which is neither True nor False:

>>> type(1 in ())
<type 'bool'>
>>> 1 in () == True, 1 in () == False
(False, False)

However, normal behaviour resumes if the original expression is parenthesized

>>> (1 in ()) == False
True

or its value is stored in a variable

>>> value = 1 in ()
>>> value == False
True

This behaviour is observed in both Python 2 and Python 3.

Can you explain what is going on?

Astrionics answered 3/11, 2013 at 9:38 Comment(0)
L
45

You are running into comparison operator chaining; 1 in () == False does not mean (1 in ()) == False.

Rather, comparisons are chained and the expression really means:

(1 in ()) and (() == False)

Because (1 in ()) is already false, the second half of the chained expression is ignored altogether (since False and something_else returns False whatever the value of something_else would be).

See the comparisons expressions documentation:

Comparisons can be chained arbitrarily, e.g., 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).

For the record, <, >, ==, >=, <=, !=, is, is not, in and not in are all comparison operators (as is the deprecated <>).

In general, don't compare against booleans; just test the expression itself. If you have to test against a boolean literal, at least use parenthesis and the is operator, True and False are singletons, just like None:

>>> (1 in ()) is False
True

This gets more confusing still when integers are involved. The Python bool type is a subclass of int1. As such, False == 0 is true, as is True == 1. You therefor can conceivably create chained operations that almost look sane:

3 > 1 == True

is true because 3 > 1 and 1 == True are both true. But the expression:

3 > 2 == True

is false, because 2 == True is false.

1 bool is a subclass of int for historic reasons; Python didn't always have a bool type and overloaded integers with boolean meaning just like C does. Making bool a subclass kept older code working.

Lucier answered 3/11, 2013 at 9:41 Comment(3)
Thank you, that makes perfect sense. Wouldn't dream of comparing against a boolean expression in real life, but stumbled over this curiosity while trying to explain to someone why you shouldn't do it.Astrionics
@user2949478: and now you have another reason! :-)Lucier
This looks to me like it violates the principle of least astonishment. I can see expressions like a is b is None, and 1 < x < 10, but not mixes between in, not in, is, is not, with each other, or with the (in)equality operators.Yetty

© 2022 - 2024 — McMap. All rights reserved.