Python三操作数比较在底层是如何工作的?

3
请问如何解释链式比较的句法分析树长什么样子?
据我理解,大多数语言都是基于运算符结合性构造节点,因此在 a < b < c 中,将会有一个布尔值作为左操作数或右操作数。
但在Python中,这种表达式几乎等同于 a < b and b < c(其中 b 只被计算一次)。
这种转换的生成语法规则是什么?基本上,Python解释器在这种情况下如何构造解析树?

语法规则简单来说就是:comparison: expr (comp_op expr)* - Martijn Pieters
这种表达式几乎等同于 a < b and b < ca < b < ca < b and b < c 完全相同,只是保证 b 表达式最多被评估一次。 - PM 2Ring
@PM2Ring 是的,这就是为什么我写了“几乎”,如果在计算中没有副作用,它们是等价的。 - Alexander Reshytko
明白了。我只是为了其他读者的好处添加了我的评论。 - PM 2Ring
1个回答

8

比较语法在这里并不是那么有趣,它只是让您将多个比较器附加到运算符上:

comparison    ::=  or_expr ( comp_operator or_expr )*
comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "!="
                   | "is" ["not"] | ["not"] "in"

那么让我们直接询问Python解析器,使用ast模块(它只会向Python编译器请求返回抽象语法树):

>>> import ast
>>> ast.dump(ast.parse('a > b > c', mode='eval'))
"Expression(body=Compare(left=Name(id='a', ctx=Load()), ops=[Gt(), Gt()], comparators=[Name(id='b', ctx=Load()), Name(id='c', ctx=Load())]))"

因此,只有一个单一的比较节点,具有多个运算符和比较器:

Compare(
    left=Name(id='a'),
    ops=[Gt(), Gt()],
    comparators=[Name(id='b'), Name(id='c')])

这使解释器可以根据需要评估比较器(例如,如果 a < b 为假,则无需考虑剩余的比较器)。

生成的字节码使用条件跳转来跳过剩余的比较:

>>> import dis
>>> dis.dis(compile('a > b > c', '', 'eval'))
  1           0 LOAD_NAME                0 (a)
              2 LOAD_NAME                1 (b)
              4 DUP_TOP
              6 ROT_THREE
              8 COMPARE_OP               4 (>)
             10 JUMP_IF_FALSE_OR_POP    18
             12 LOAD_NAME                2 (c)
             14 COMPARE_OP               4 (>)
             16 RETURN_VALUE
        >>   18 ROT_TWO
             20 POP_TOP
             22 RETURN_VALUE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接