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

How is ternary operator implemented in Python

我理解条件表达式(或三元运算符)在Python中是懒惰的。它们表示条件执行,而不是条件选择。换言之,只有一个ab的评估如下:

1
c = a if condition else b

我感兴趣的是这是如何在内部实现的。python是否如下面所示转换为if语句,如果是,转换将在哪个阶段进行?

1
2
3
4
if condition:
    c = a
else:
    c = b

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

我看过下面解释三元运算符做什么的内容,但没有一个解释它们是如何实现的:

  • python是否有三元条件运算符?
  • 将简单的if-then-else语句放在一行上
  • Python?(条件/三元)赋值运算符
  • 是否有相当于c的?:"三元运算符?"
  • 条件表达式

编辑:可以假设cpython引用实现。


python不需要转换任何东西,如果愿意的话也不能转换。

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

1
2
3
4
5
>>> 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模块抽象语法部分有记录:

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

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

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

1
2
3
4
5
6
7
8
9
10
>>> 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语句包含一个条件表达式和一个套件,该套件必须始终包含更多的语句(有"expression statement"这样的东西可以让您在一个语句中只放一个表达式,例如1 + 1在一行上),这些语句可以"做"诸如赋值或从函数返回之类的事情,但不能他们真的会让if返回一些东西。

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

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

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

所以这对cpython来说不是唯一的,这适用于所有的python实现。python语法不是实现细节。


Does Python convert to an if statement as below

几乎。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

此输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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的位置。

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

1
2
3
4
5
6
from timeit import Timer

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

至于这种转换何时发生,我假设在"编译"到字节码的过程中。


什么

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

value_a if condition else value_b是函数,返回值value_avalue_b

1
2
3
4
if condition then:
   do_a
else:
   do_b

是程序性的,它执行do_ado_b

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

怎么

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