Python中的三元运算符是如何实现的

Posted

技术标签:

【中文标题】Python中的三元运算符是如何实现的【英文标题】:How is ternary operator implemented in Python 【发布时间】:2019-02-11 14:03:53 【问题描述】:

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

c = a if condition else b

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

if condition:
    c = a
else:
    c = b

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

我查看了以下解释三元运算符的什么,但没有一个说明如何它们是如何实现的:

Does Python have a ternary conditional operator? Putting a simple if-then-else statement on one line python ? (conditional/ternary) operator for assignments Is there an equivalent of C’s ”?:” ternary operator? Conditional expressions

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

【问题讨论】:

实施将取决于实施:只要行为正确,就可以按照实施者的意愿进行。在您的问题中,您在如何和什么之间切换。你有兴趣怎么做吗?如果是这样,请查看其中一种实现的源代码。或者你对什么感兴趣? IE。 “具体的行为是什么?” @ctrl-alt-delor,我应该提到你可以假设参考 CPython 实现。 【参考方案1】:

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

使用language grammar 将条件表达式解析为abstract syntax tree,然后将其编译为字节码。您可以使用 ast.parse() function 生成 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 中的 ast.IfExp() 节点;这是条件表达式的专用节点。它有testbodyorelse 部分来表示构成条件的3 个表达式,true 和false 部分。这记录在ast module Abstract Grammar section:

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

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

然后解析树被编译成字节码,使用栈根据测试条件跳转到正确的部分;我们可以将ast.parse()产生的AST直接传递给compile() function,然后dis module让我们看一下编译产生的对人类友好的字节码形式:

>>> 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 : ...)。 if 语句包含一个 条件表达式 和一个 suite,它必须始终包含更多语句(有一个“表达式语句”之类的东西可以让你放只是语句中的一个表达式,例如单行上的1 + 1),这些语句可以“做一些事情”,比如赋值或从函数返回,但它们所做的任何事情都不会让if 返回一些东西。

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

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

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

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

【讨论】:

澄清一下,这是CPython使用的语言语法,还是所有python实现都必须尊重的官方东西? @Aran-Fey:这是所有实现都必须遵守的官方规定。这是语法的一部分。 @Aran-Fey:其他实现无法做任何不同的事情,因为您无法将表达式转换为语句并且仍然能够在其他表达式中使用它。【参考方案2】:

Python 是否会转换为如下的 if 语句

差不多了。

import dis

def trenary():
    x = 'a' if 1 == 1 else 'b'

def normal_if():
    if 1 == 1:
        c = 'a'
    else:
        c = 'b'

print('trenary')
dis.dis(trenary)
print()
print('normal if')
dis.dis(normal_if)

这个输出:

trenary
 68           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE       12
              8 LOAD_CONST               2 ('a')
             10 JUMP_FORWARD             2 (to 14)
        >>   12 LOAD_CONST               3 ('b')
        >>   14 STORE_FAST               0 (x)
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

normal if
 71           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               1 (1)
              4 COMPARE_OP               2 (==)
              6 POP_JUMP_IF_FALSE       14

 72           8 LOAD_CONST               2 ('a')
             10 STORE_FAST               0 (c)
             12 JUMP_FORWARD             4 (to 18)

 74     >>   14 LOAD_CONST               3 ('b')
             16 STORE_FAST               0 (c)
        >>   18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

这些看起来几乎相同,除了JUMP_FORWARD 的位置和@L3viathan 指出的另外一个STORE_FAST

我们也得到几乎相同的执行时间(差异可以忽略不计):

from timeit import Timer

print(min(Timer(trenary).repeat(5000, 5000)))
print(min(Timer(normal_if).repeat(5000, 5000)))
# 0.0006442809999998023
# 0.0006442799999994975

至于何时发生这种转换,我会假设在“编译”为字节码期间的某个时间。

【讨论】:

我们还有一个额外的STORE_FAST【参考方案3】:

什么

如果您要问什么,那么为了更好地理解它,您需要了解功能性和程序性之间的区别。一种可以转换为另一种,但两者都可以独立查看,您无需将一种翻译为另一种即可理解它们。

value_a if condition else value_b 具有功能性,并返回值 value_avalue_b

if condition then:
   do_a
else:
   do_b

是程序性的,它是do_ado_b

注意:程序是关于做,做这个,然后做那个,或者那个。功能就是价值,是这样还是那样。

如何

如果您要问如何,那么您需要查看其中一种实现的源代码。请注意,只要行为正确,每个实现不必采用相同的方式。

【讨论】:

以上是关于Python中的三元运算符是如何实现的的主要内容,如果未能解决你的问题,请参考以下文章

Python中的三元运算

python中的三元运算

python 10 迭代器和三元运算符

python 三元运算

python 中的三元运算符

如何在python中编写三元条件运算符? [复制]