False == False in [False]
# Outputs True
It doesn't make sense no matter which way you evaluate it. Equality first: False == False # True
True in [False] # False
or `in` first: False in [False] # True
False == True # False False == False in [False]
# Outputs True
It doesn't make sense no matter which way you evaluate it. Equality first: False == False # True
True in [False] # False
or `in` first: False in [False] # True
False == True # FalseIt makes sense when you evaluate it the way it's intended to be understood (https://stackoverflow.com/questions/6074018). There are other languages that do this, and it was consciously designed to work this way. The idea that you should have to evaluate one of the operators "first" may make sense to someone trying to implement a parser with techniques from the 70s, but for languages like Python, the design is clearly more about what makes sense for the code author.
Arguably it's surprising that `in` is treated as on par with `==`. But on the other hand, even someone trying to implement a parser with techniques from the 70s would have to admit that both are "relational operators".
False == False and False in [False] is a expansion of that statement, just like x < y and y < z is the expansion of x < y < z
I'll add:
>>> False == False in [True]
False
>>> True == True in [True]
True
ps: some ast poking >>> ast.show(ast.parse("False == False in [False]"))
Module(
body=[
Expr(
value=Compare(
left=Constant(value=False),
ops=[
Eq(),
In()],
comparators=[
Constant(value=False),
List(
elts=[
Constant(value=False)],
ctx=Load())]))])
>>> ast.show(ast.parse("(False == False) in [False]"))
Module(
body=[
Expr(
value=Compare(
left=Compare(
left=Constant(value=False),
ops=[
Eq()],
comparators=[
Constant(value=False)]),
ops=[
In()],
comparators=[
List(
elts=[
Constant(value=False)],
ctx=Load())]))])
note the first tree as one Comparator node with multiple ops, time to read the spec to know how these are evaluated False == False in [True]
=>
(False == False) and (False in [True])
If you put it into a function like this: def foo(): return False == False in [True]
And then disassemble it you'll get this byte code (3.13, may vary by version): RESUME 0
LOAD_CONST 1 (False)
LOAD_CONST 1 (False)
SWAP 2
COPY 2
COMPARE_OP 72 (==)
COPY 1
TO_BOOL
POP_JUMP_IF_FALSE 5 (to L1)
POP_TOP
LOAD_CONST 2 ((True,))
CONTAINS_OP 0
RETURN_VALUE
L1: SWAP 2
POP_TOP
RETURN_VALUE
There is no literal `and` operation occurring here, instead it first does the `False == False`, then jumps if false or continues to the second comparison.For the literals we've given it, it'll go on to the second comparison which will result in false since `False` is not in `(True,)`.
EDIT: And here's the non-chained version disassembled:
def bar(): return (False == False) in [True]
RESUME 0
LOAD_CONST 1 (False)
LOAD_CONST 1 (False)
COMPARE_OP 72 (==)
LOAD_CONST 2 ((False,))
CONTAINS_OP 0
RETURN_VALUE return False == False in [False]
is roughly a = False
b = False
c = [False]
(a == b) and (b in c)
is less roughly a = False
b = False
if not (a == b):
# short circuits https://news.ycombinator.com/item?id=44619401
return False
c = [False]
if not (b in c):
return False
return TrueSo for example `1 in [1,2] in [[1,2],[3,4]]` is True... but off the top of my head I don't see any legitimate use-case for that facility. Intuitively, I'd think a "comparison operator" should be homogeneous — should take the same type on LHS and RHS. So, like, "is-subset-of" could sanely be a comparison operator, but `in` can't be.
Python documentation calls it a "comparison operator" (https://docs.python.org/3/reference/expressions.html#compari...) but a broader, better term is "relational operator". It expresses a possible relationship between two arguments, and evaluates whether those arguments have that relationship. In this case, "containment" (which intuitively encompasses set membership and substrings).
A finer distinction could have been made, arguably, but it's far too late to change now. (And everyone is traumatized by the 2->3 transition.)