Python 中的链式比较实际上是如何工作的?

Posted

技术标签:

【中文标题】Python 中的链式比较实际上是如何工作的?【英文标题】:How do chained comparisons in Python actually work? 【发布时间】:2015-04-29 13:18:30 【问题描述】:

Python Doc for Comparisons 说:

比较可以任意链接,例如,x < y <= z 等价于 x < y and y <= z,除了 y 只被评估一次(但在这两种情况下,z 根本不被评估,当 x < y 被发现是假的)。

这些 SO 问题/答案进一步阐明了这种用法:

Python comparison operators chaining/grouping left to right? What does "evaluated only once" mean for chained comparisons in Python?,尤其是the currently-accepted answer

类似(人为的例子):

if 1 < input("Value:") < 10: print "Is greater than 1 and less than 10"

只要求输入一次。这是有道理的。还有这个:

if 1 < input("Val1:") < 10 < input("Val2:") < 20: print "woo!"

只要求Val2 如果 Val1 介于 1 和 10 之间,并且只打印“woo!” 如果 Val2 也在 10 到 20 之间(证明它们可以“任意链接”)。这也是有道理的。

但我仍然很好奇这是如何在词法分析器/解析器/编译器(或其他)级别实际实现/解释的。

上面的第一个例子基本上是这样实现的吗:

x = input("Value:")
1 < x and x < 10: print "Is between 1 and 10"

x 真的只存在于那些比较中(实际上基本上是未命名的)?或者它是否以某种方式使比较运算符返回布尔结果和正确操作数的评估(用于进一步比较)或类似的东西?

将分析扩展到第二个示例让我相信它使用了类似未命名的中间结果(如果有这个术语,请告诉我),因为它不会在进行比较之前评估所有操作数。

【问题讨论】:

【参考方案1】:

您可以简单地让 Python 告诉您使用 dis module 生成的字节码:

>>> import dis
>>> def f(): return 1 < input("Value:") < 10
... 
>>> dis.dis(f)
  1           0 LOAD_CONST               1 (1)
              3 LOAD_GLOBAL              0 (input)
              6 LOAD_CONST               2 ('Value:')
              9 CALL_FUNCTION            1
             12 DUP_TOP             
             13 ROT_THREE           
             14 COMPARE_OP               0 (<)
             17 JUMP_IF_FALSE_OR_POP    27
             20 LOAD_CONST               3 (10)
             23 COMPARE_OP               0 (<)
             26 RETURN_VALUE        
        >>   27 ROT_TWO             
             28 POP_TOP             
             29 RETURN_VALUE        

Python 使用堆栈; CALL_FUNCTION bytecode 使用堆栈上的项目(input 全局和 'Value:' 字符串)调用带有一个参数的函数,用函数调用的结果替换堆栈上的这两个项目。在函数调用之前,常量1被加载到栈中。

所以当input 被调用时,堆栈看起来像:

input_result
1

并且DUP_TOP 复制顶部值,在rotating the top three stack 值到达之前:

1
input_result
input_result

COMPARE_OP&lt; 测试前两项,用结果替换前两项。

如果该结果是False,则JUMP_IF_FALSE_OR_POP bytecode 会跳到27,这会将False 与剩余的input_result 一起旋转以使用POP_TOP 将其清除,然后返回剩余的@987654343 @top 值作为结果。

如果结果为True,则该值由JUMP_IF_FALSE_OR_POP 字节码从堆栈中弹出,并在其位置上加载10 值,我们得到:

10    
input_result

然后进行另一个比较并返回。

总之,Python 基本上就是这样做的:

stack_1 = stack_2 = input('Value:')
if 1 < stack_1:
    result = False
else:
    result = stack_2 < 10

stack_* 值再次被清除。

然后,堆栈保存 未命名的中间结果以进行比较

【讨论】:

太棒了,谢谢!不知道dis,这很酷。将此扩展到第二个示例表明它在做同样的事情(这当然是有道理的) - 它将先前的结果留在堆栈中,将输入获取到堆栈中,然后进行下一次比较。甜! 我认为你做错了 ROT_THREE。应该使堆栈 [input_result, 1, input_result]。我会修复它,但不知道您想如何更正 “本质上是 Python 然后执行此操作” 部分。

以上是关于Python 中的链式比较实际上是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

python中的链式调用父初始化器[重复]

熊猫:链式作业[重复]

在比较 Java 中的两个原语与两个对象时,== 实际上是相同还是不同?

Python链式属性访问中的无传播[重复]

[Python]解决python链式extend的技巧

Python中的字符串比较:is vs. == [重复]