Hacker News new | past | comments | ask | show | jobs | submit login

"pretty unintuitive" is an understatement...

  In [1]: import dis
  
  In [2]: def a():
     ...:     return (1 in [1,2,3] is True)
  
  In [3]: def b():
      ..:     return ((1 in [1,2,3]) is True)
  
  In [4]: a()
  Out[4]: False
  
  In [5]: b()
  Out[5]: True
  
  In [6]: dis.dis(a)
    2           0 LOAD_CONST               1 (1)
                2 LOAD_CONST               1 (1)
                4 LOAD_CONST               2 (2)
                6 LOAD_CONST               3 (3)
                8 BUILD_LIST               3
               10 DUP_TOP
               12 ROT_THREE
               14 COMPARE_OP               6 (in)
               16 JUMP_IF_FALSE_OR_POP    24
               18 LOAD_CONST               4 (True)
               20 COMPARE_OP               8 (is)
               22 RETURN_VALUE
          >>   24 ROT_TWO
               26 POP_TOP
               28 RETURN_VALUE
  
  In [7]: dis.dis(b)
    2           0 LOAD_CONST               1 (1)
                2 LOAD_CONST               5 ((1, 2, 3))
                4 COMPARE_OP               6 (in)
                6 LOAD_CONST               4 (True)
                8 COMPARE_OP               8 (is)
               10 RETURN_VALUE
Why?!



The disassembly in parent comment is made with CPython 3.6.9, but nothing substantial changes on CPython 3.8.x/3.9.x or PyPy.

Maybe it is related to grammar, and not to compiler...

AST dump in CPython 3.6.9 (manually formatted):

  In [1]: import ast
  
  In [2]: print(ast.dump(ast.parse("""\
     ...: def a():
     ...:     return (1 in [1,2,3] is True)""")))
  
  Module(
      body=[
          FunctionDef(
              name='a',
              args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
              body=[
                  Return(
                      value=Compare(
                          left=Num(n=1),
                          ops=[In(), Is()],
                          comparators=[
                              List(elts=[Num(n=1), Num(n=2), Num(n=3)], ctx=Load()),
                              NameConstant(value=True)
                          ]
                      )
                  )
              ],
              decorator_list=[],
              returns=None
          )
      ]
  )
  
  In [3]: print(ast.dump(ast.parse("""\
     ...: def b():
     ...:     return ((1 in [1,2,3]) is True)""")))
  
  Module(
      body=[
          FunctionDef(
              name='b',
              args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
              body=[
                  Return(
                      value=Compare(
                          left=Compare(
                              left=Num(n=1),
                              ops=[In()],
                              comparators=[List(elts=[Num(n=1), Num(n=2), Num(n=3)], ctx=Load())]
                          ),
                          ops=[Is()],
                          comparators=[NameConstant(value=True)]
                      )
                  )
              ],
              decorator_list=[],
              returns=None
          )
      ]
  )


OK, mystery solved. In Python:

  1 in [1,2,3] is True
is evaluated as

  (1 in [1, 2, 3]) and ([1, 2, 3] is True)
similarly to

  1 < 2 < 3
:facepalm:


I don't see anything surprising. "is" has higher prevalenz than "in" and that's it. Or am I misreading something?


It's not a matter of operator precedence for two reasons:

1) "in" and "is" have same precedence, and group left to right (see https://docs.python.org/3/reference/expressions.html#operato...)

2) you'll have runtime error:

  In [1]: def c():
     ...:     return (1 in ([1,2,3] is True))
  
  In [2]: c()
  ...  
  TypeError: argument of type 'bool' is not iterable
It seems related to CPython bytecode compiler implementation, the two functions are parsed in a different way, parentheses make the compiler go on a different path... but I'd like to understand why, without diving into CPython source code :) Anyone?


If this was the reason you'd get a TypeError, since there's no `bool.__contains__`.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: