首页 > 解决方案 > Python中的三元运算符是如何实现的

问题描述

我知道条件表达式(或三元运算符)在 Python 中是惰性的。它们代表条件执行而不是条件选择。换句话说,只有一个ab在以下情况下被评估:

c = a if condition else b

我很想知道这是如何在内部实现的。Python 是否转换if为如下语句,如果是,这种转换发生在什么阶段?

if condition:
    c = a
else:
    c = b

或者三元运算符实际上是一个不同且独立的表达式,完全分开定义?如果是这样,我可以访问条件表达式的 CPython 代码吗?

我查看了以下内容,这些内容解释三元运算符的作用,但没有一个说明它们是如何实现的:


编辑:您可以假设 CPython 参考实现。

标签: pythonpython-3.xternary-operatorcpythonpython-internals

解决方案


Python 不需要转换任何东西,如果它愿意也不能。

通过使用语言语法将条件表达式解析为抽象语法树,然后将其编译为字节码。您可以使用以下ast.parse()函数生成 AST :

>>> import ast
>>> ast.parse('c = a if condition else b').body[0]  # first statement in the tree
<_ast.Assign object at 0x10f05c550>
>>> ast.dump(ast.parse('c = a if condition else b').body[0])
"Assign(targets=[Name(id='c', ctx=Store())], value=IfExp(test=Name(id='condition', ctx=Load()), body=Name(id='a', ctx=Load()), orelse=Name(id='b', ctx=Load())))"

注意ast.IfExp()AST 中为分配生成的节点;这是条件表达式的专用节点。它有test,bodyorelse部分来表示构成条件的 3 个表达式,true 和 false 部分。这记录在ast模块抽象语法部分中:

expr = [...]
     | [...]
     | IfExp(expr test, expr body, expr orelse)

这表明每个元素的类型是另一个expr表达式节点。

然后解析树被编译成字节码,使用堆栈根据测试有条件地跳转到正确的部分;我们可以将生成的 ASTast.parse()直接传递给compile()函数,然后dis模块让我们查看编译生成的对人类友好的字节码形式:

>>> import dis
>>> dis.dis(compile(ast.parse('c = a if condition else b'), '', 'exec'))
  1           0 LOAD_NAME                0 (condition)
              2 POP_JUMP_IF_FALSE        8
              4 LOAD_NAME                1 (a)
              6 JUMP_FORWARD             2 (to 10)
        >>    8 LOAD_NAME                2 (b)
        >>   10 STORE_NAME               3 (c)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

因此,如果条件为假,解释器循环向前跳转到指令 8,否则执行指令 4 和 6,指令 6 向前跳转到指令 10(因此通过else表达式)。最终结果是指令 4 或指令 8 将新结果放在堆栈顶部STORE_NAME以移动到变量。

一条if语句会产生不同的 AST 节点,而产生的字节码恰好非常相似,因为它也会使用跳转。但是编译器将它们视为不同的语法片段,而且它必须这样做。

表达式和语句是编程语言的两个非常不同的基本构建块。语句可以包含表达式,但表达式不能包含语句,只能包含其他表达式。并且表达式可以产生一个值(供周围的语法使用),但语句不能。因此 Python 必须以非常不同于语句的方式处理条件表达式,因为语法解析器知道何时期望语句以及何时允许表达式。如果将条件表达式转换为语句,则永远无法将这样的表达式用作更大表达式的一部分!

因为if语句不是表达式,所以它不返回值(因为只有表达式才能产生值),因此生成的字节码不会在堆栈顶部产生值,以供周围的 Python 代码使用(没有c = if condition : ...)。ifstatements 包含一个condition expression和一个suite,它必须总是包含更多的 statements(有一个'表达式语句'这样的东西可以让你在一个语句中只放置一个表达式,例如1 + 1在单行上),以及那些语句可以“做一些事情”,例如赋值或从函数返回,但它们所做的任何事情都不会if返回某些东西。

这反映在if语句的 AST 节点定义中:

stmt =  [...]
      | [...]
      | If(expr test, stmt* body, stmt* orelse)

所以对于一个If节点来说,test是唯一的表达式节点,并且body都由orelse个或多个语句组成。该orelse部分将保存任何elif ...:测试作为进一步If()的节点,或任何其他类型的语句以形成无条件的else:. 对于零个或多个元素,您不能期望一个结果。

所以这不是 CPython 独有的,它适用于所有 Python 实现。Python语法不是实现细节。


推荐阅读