Python 中的链式比较实际上是如何工作的? [英] How do chained comparisons in Python actually work?

查看:69
本文介绍了Python 中的链式比较实际上是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

用于比较的 Python 文档 说:

<块引用>

比较可以任意链接,例如,x <;y <= z 等价于 x <;y 和 y <= z,除了 y 只计算一次(但在这两种情况下,zx <; y 被发现是假的).

这些 SO 问题/答案对这种用法有更多的了解:

所以像(人为的例子):

if 1 <输入(值:")<10:打印大于1且小于10"

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

if 1 <输入(Val1:")<10<输入(Val2:")<20:打印哇!"

仅要求 Val2 if Val1 介于 1 &10 并且只打印woo!"if Val2 也在 10 到 20 之间(证明它们可以任意链接").这也有道理.

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

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

x = input("值:")1 

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

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

解决方案

您可以简单地让 Python 告诉您使用 dis 模块:

<预><代码>>>>导入文件>>>def f(): 返回 1 <输入(值:")<10...>>>dis.dis(f)1 0 LOAD_CONST 1 (1)3 LOAD_GLOBAL 0(输入)6 LOAD_CONST 2 ('值:')9 CALL_FUNCTION 112 DUP_TOP13 ROT_THREE14 COMPARE_OP 0 (<)17 JUMP_IF_FALSE_OR_POP 2720 LOAD_CONST 3 (10)23 COMPARE_OP 0 (<)26 RETURN_VALUE>>27 ROT_TWO28 POP_TOP29 RETURN_VALUE

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

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

input_result1

DUP_TOP 重复在旋转前三个堆栈值到达之前的最高值在:

1输入结果输入结果

和一个 COMPARE_OP< 测试前两项,用结果替换前两项.

如果结果是FalseJUMP_IF_FALSE_OR_POP 字节码 跳转到 27,这会将 False 与剩余的 input_result 一起旋转到顶部,以使用 清除它POP_TOP,然后返回剩余的 False 顶部值作为结果.

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

10输入结果

进行另一次比较并返回.

总而言之,本质上 Python 就是这样做的:

stack_1 = stack_2 = input('Value:')如果 1 <堆栈_1:结果 = 错误别的:结果 = stack_2 <10

再次清除 stack_* 值.

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

The Python Doc for Comparisons says:

Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

And these SO questions/answers shed some more light on such usage:

So something like (contrived example):

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

only asks for input once. This makes sense. And this:

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

only asks for Val2 if Val1 is between 1 & 10 and only prints "woo!" if Val2 is also between 10 and 20 (proving they can be 'chained arbitrarily'). This also makes sense.

But I'm still curious how this is actually implemented/interpreted at the lexer/parser/compiler (or whatever) level.

Is the first example above basically implemented like this:

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

where x really only exists (and is actually essentially unnamed) for those comparisons? Or does it somehow make the comparison operator return both the boolean result and the evaluation of the right operand (to be used for further comparison) or something like that?

Extending analysis to the second example leads me to believe it's using something like an unnamed intermediate result (someone educate me if there's a term for that) as it doesn't evaluate all the operands before doing the comparison.

解决方案

You can simply let Python tell you what bytecode is produced with the 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 uses a stack; the CALL_FUNCTION bytecode uses items on the stack (the input global and the 'Value:' string) to call a function with one argument, to replace those two items on the stack with the result of the function call. Before the function call, the the constant 1 was loaded on the stack.

So by the time input was called the stack looks like:

input_result
1

and DUP_TOP duplicates the top value, before rotating the top three stack values to arrive at:

1
input_result
input_result

and a COMPARE_OP to test the top two items with <, replacing the top two items with the result.

If that result was False the JUMP_IF_FALSE_OR_POP bytecode jumps over to 27, which rotates the False on top with the remaining input_result to clear that out with a POP_TOP, to then return the remaining False top value as the result.

If the result True however, that value is popped of the stack by the JUMP_IF_FALSE_OR_POP bytecode and in it's place the 10 value is loaded on top and we get:

10    
input_result

and another comparison is made and returned instead.

In summary, essentially Python then does this:

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

with the stack_* values cleared again.

The stack, then, holds the unnamed intermediate result to compare

这篇关于Python 中的链式比较实际上是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆