【问题标题】:How does three operands comparison work in Python under the hood?三个操作数比较在 Python 的底层是如何工作的?
【发布时间】:2016-11-28 10:35:30
【问题描述】:

您能否解释一下用于链式比较的句法分析树是什么样的?

据我了解,在大多数语言中,它基于运算符关联性构造节点,因此在 a < b < c 中将有一个布尔值作为左手或右手操作数。

但在 Python 中,这样的表达式几乎等同于 a < b and b < cb 只计算一次)。

这种转换的生成语法规则是什么?基本上,在这种情况下,Python 解释器如何构建解析树?

【问题讨论】:

  • 语法规则就是comparison: expr (comp_op expr)*
  • "这样的表达几乎等同于a < b and b < c"。 a < b < ca < b and b < c 完全等价,除了 b 表达式保证被评估一次(最多)。
  • @PM2Ring 是的,这就是为什么我写“几乎”它们是等效的,如果在计算 b 时没有副作用
  • 明白。为了其他读者的利益,我只是添加了我的评论。

标签: python parsing comparison-operators python-internals


【解决方案1】:

comparison grammar 在这里并不有趣,它只是让您将多个比较器附加到一个运算符:

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

所以让我们直接询问 Python 解析器,使用 ast module(它只是要求 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 节点,有多个运算符和比较器:

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

(我省略了Expressionctx 部分)。

这让解释器可以根据需要评估比较器(例如,如果 a &lt; 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

【讨论】:

    猜你喜欢
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-29
    相关资源
    最近更新 更多