如何在Python中实现三元运算符

时间:2018-09-06 10:18:49

标签: python python-3.x ternary-operator cpython python-internals

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

c = a if condition else b

我想知道的是如何在内部实现。 Python是否会按以下方式转换为if语句?如果是,转换会在什么阶段进行?

if condition:
    c = a
else:
    c = b

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

我看过下面的内容,这些内容解释了三元运算符的作用,但没有一个人弄清楚它们的实现方式:


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

3 个答案:

答案 0 :(得分:11)

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个表达式,即真假部分。 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

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

一条if语句导致一个不同的AST节点,结果字节码 happens 非常相似,因为它也会使用跳转。但是编译器将它们视为不同的语法,并且已经

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

由于if语句不是表达式,因此它不会返回一个值(因为只有表达式才能产生值),因此最终的字节码不会在堆栈的顶部产生要由周围的Python代码使用的值(没有c = if condition : ...)。 if语句包含一个条件表达式和一个 suite ,该表达式必须始终由更多的语句组成(存在诸如“ expression statement”之类的内容,您只需在表达式中放置一个表达式(例如,1 + 1在一行中),这些语句就可以像分配或“返回”功能一样“做些事情”,但是它们所做的却无济于事if返回一些东西。

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

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

因此,对于If节点,test是唯一的表达式节点,并且bodyorelse都由零个或更多语句组成。 orelse部分将保存任何elif ...:个测试,作为另外的If()个节点,或者保留任何其他类型的语句以形成无条件的else:。使用零个或多个元素,您将无法期待一个结果。

因此,这并非CPython所独有,这适用于所有Python实现。 Python 语法不是实现细节。

答案 1 :(得分:1)

  

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

关于何时发生这种转换,我会在“编译”为字节码的过程中假设

答案 2 :(得分:0)

什么

如果您要问什么,那么为了最好地理解它,您需要了解功能和过程之间的区别。一个可以转换为另一个,但是两者都可以独立查看,您不必将一个转换为另一个即可理解它们。

value_a if condition else value_b是有效的,并返回值value_avalue_b

if condition then:
   do_a
else:
   do_b

是程序性的,它执行do_ado_b

注意:程序是关于执行,执行,然后执行或执行的操作。功能是关于价值的,是这个还是那个。

方法

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