Here’s an image-to-text translation:

>>> True is not False
True

>>> True is not False is True
False

>>> (True is not False) is True
True

>>> True is not (False is True)
True

>>> True is (not False is True)
True

>>> True is not False is False
True

>>> wtf
True

The True is not False is False one surprised me.

This got me curious, so I looked a bit into it and disassembled the bytecode for these expressions.

TL;DR: The interpreter, for some reason, transforms True is not False is True into False is True, while also doing some other stuff and immediately discarding the results of this stuff.

Starting with:

def a():
    return True is not (False is True)

Then running dis.dis(a) yields:

 11           0 LOAD_CONST               1 (True)
              2 LOAD_CONST               2 (False)
              4 LOAD_CONST               1 (True)
              6 COMPARE_OP               8 (is)
              8 COMPARE_OP               9 (is not)
             10 RETURN_VALUE

This is fairly straightforward. Looking at the state of the stack through this process:

Opcode Description Stack
0 LOAD_CONST 1 (True) Push True on top of the stack [True]
2 LOAD_CONST 2 (False) Push False on top of the stack [FalseTrue]
4 LOAD_CONST 1 (True) Push True on top of the stack [TrueFalseTrue]
6 COMPARE_OP 8 (is) Pop the top 2 elements off the stack, compare them using is and push the result on top of stack [FalseTrue]
8 COMPARE_OP 9 (is not) Pop the top 2 elements off the stack, compare them using is not and push the result on top of stack [True]
10 RETURN_VALUE Return top of stack

Pretty much what is expected. The results are very similar with True is (not False is True) and (True is not False) is True, so I won’t go into details. But when trying the same thing with True is not False is True, WTF?

  9           0 LOAD_CONST               1 (True)
              2 LOAD_CONST               2 (False)
              4 DUP_TOP
              6 ROT_THREE
              8 COMPARE_OP               9 (is not)
             10 JUMP_IF_FALSE_OR_POP    18
             12 LOAD_CONST               1 (True)
             14 COMPARE_OP               8 (is)
             16 RETURN_VALUE
        >>   18 ROT_TWO
             20 POP_TOP
             22 RETURN_VALUE
Opcode Description Stack
0 LOAD_CONST 1 (True) Push True on top of the stack [True]
2 LOAD_CONST 2 (False) Push False on top of the stack [FalseTrue]
4 DUP_TOP Duplicate top of stack. WTF? [FalseFalseTrue]
6 ROT_THREE Lifts second and third stack item one position up, moves top down to position three [FalseTrueFalse]
8 COMPARE_OP 9 (is not) Pop the top 2 elements off the stack, compare them using is not and push the result on top of stack [TrueFalse]
10 JUMP_IF_FALSE_OR_POP 18 Jump to label 18 if top of stack is false. This will never happen. Otherwise, pop (discard) top of stack, which means the result of the previous comparison is discarded. The only thing left is the second constant loaded. This makes no sense [False]
12 LOAD_CONST 1 (True) Push True on top of the stack [TrueFalse]
14 COMPARE_OP 8 (is) Pop the top 2 elements off the stack, compare them using is and push the result on top of stack [False]
16 RETURN_VALUE return top of stack
18 ROT_TWO Swap the 2 topmost elements of the stack. This will never happen, but I’m assuming the branch is somehow taken… [FalseTrue]
20 POP_TOP Discard top of stack [True]
22 RETURN_VALUE return top of stack. This happens to be the corect value, but it is not the result of the comparisons in the code (the is not is ignored in this case)

I don’t know why this happens, but the Python interpreter gets very confused by this expression, even introducing a branch in an expression where only constants are loaded. This is really weird.

Edit: I tried a few more variations of the expressions, it seems like adding parentheses almost anywhere puts the interpreter back on track, and changing the values of the booleans has no effect. Still no clue on why it fucks up, though.

余下全文(1/3)
分享这篇文章:

请关注我们:

发表评论

电子邮件地址不会被公开。 必填项已用*标注