注意:这个答案让我对 Python 链式条件句的语义有了一次有趣的了解。我没有删除我最初的错误答案,而是将我的研究链保留为下面的一系列 EDIT 语句。对于真正的答案,您可以跳到底部、阅读 cmets 或查看该问题的其他几个有价值的答案。
因为operator precedence的规则。在 Python 中,== 运算符和 in 运算符具有相同的优先级,因此它们按从左到右的顺序计算。换句话说,你写了相当于assert( (True == 'a') in 'apple')。由于True != 'a',你在断言False in 'apple',同样为假,断言失败。
与True 和False 的布尔比较是多余的,可以消除。更简洁的说法是:
assert 'a' in 'apple'
编辑:下面已经指出assert( (True == 'a') in 'apple' ) 实际上引发了一个不同的异常 (TypeError)。我试图通过以下反汇编来理解这一点:
>>> 定义原点():
... 断言 True == 'a' in 'apple'
...
>>> 定义括号():
... assert( (True == 'a') in 'apple')
...
>>> 导入磁盘
>>> dis.dis(原版)
2 0 LOAD_GLOBAL 0(真)
3 LOAD_CONST 1 ('a')
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 2 (==)
11 跳转_IF_FALSE_OR_POP 23
14 LOAD_CONST 2 ('苹果')
17 COMPARE_OP 6 (in)
20 JUMP_FORWARD 2(至 25)
>> 23 ROT_TWO
24 POP_TOP
>> 25 POP_JUMP_IF_TRUE 34
28 LOAD_GLOBAL 1(断言错误)
31 RAISE_VARARGS 1
>> 34 LOAD_CONST 0(无)
37 返回值
>>> dis.dis(paren)
2 0 LOAD_GLOBAL 0(真)
3 LOAD_CONST 1 ('a')
6 比较_OP 2 (==)
9 LOAD_CONST 2 ('苹果')
12 COMPARE_OP 6 (in)
15 POP_JUMP_IF_TRUE 24
18 LOAD_GLOBAL 1(断言错误)
21 RAISE_VARARGS 1
>> 24 LOAD_CONST 0(无)
27 返回值
>>>
这实际上表明 Python 已将比较运算符(== 和 in)解释为可以快速失败为 False 的链,从而导致 AssertionError。一旦True == 'a' 被评估为 False,Python 就推断整个语句必须为 false,并触发断言失败,而不继续尝试评估 False in 'apple'。前面加括号解释的时候,强制解释器按顺序计算每个嵌套的括号,所以不能使用short-circuit evaluation,避免后面的异常。
编辑 2:我的短路评估假设似乎也不完整。真正的答案在于 Python 处理链式条件的方式:作为由布尔 and 连接的一系列单独的布尔比较。这是我在上面反汇编的字节码的演练,使用 Python 提示符将解释器的堆栈表示为列表:
>>> stack = [] # 空栈
>>> stack.append(True) # LOAD_GLOBAL 0 (True)
>>> stack.append('a') # LOAD_CONST 1 ('a')
>>> stack.append(stack[-1]) # DUP_TOP
>>> 堆栈
[真,'a','a']
>>> stack[-3],stack[-2],stack[-1] = stack[-1], stack[-3], stack[-2] # ROT_THREE
>>> 堆栈
['a',真,'a']
>>> stack.append(stack[-2] == stack[-1])
>>> 堆栈
['a',真,'a',假]
>>> # JUMP_IF_FALSE_OR_POP 23
此时,如果第一个条件 (True == 'a') 为真,我们将从堆栈顶部弹出 True 值并继续评估下一个值 ('a') 是否为 in 'apple'(行14 和 17)。基于True == 'a' in 'apple' 等同于(True == 'a') and ('a' in 'apple') 的事实,这是我们应该期待的。 JUMP_IF_FALSE_OR_POP 实现了布尔值and 链的短路求值:一旦遇到一个假条件,整个表达式就必须为假。
第 20 行的JUMP_FORWARD 2 (to 25) 表示两个分支在第 25 行汇合,这是检查链式条件的最终结果的地方。让我们回顾一下真正的执行链,根据False 结果跳转到 23:
>>> stack[-2], stack[-1] = stack[-1], stack[-2] # ROT_TWO
>>> 堆栈
['a',真,假,'a']
>>> stack.pop() # POP_TOP
'一种'
>>> 堆栈
['a',真,假]
>>> stack.pop() # POP_JUMP_IF_TRUE 34
错误的
现在我们到达另一个条件跳转,但我们不接受它,因为结果是False,而不是True。相反,我们继续执行第 28 和 31 行,它们准备并引发 AssertionError。
最后一点:正如documentation for the dis library 所指出的,字节码是CPython 的一个实现细节,不保证在版本之间是相同的。我正在使用 Python 2.7.3。