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

14 浏览
0 Comments

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

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

c = a if condition else b

我感兴趣的是如何在内部实现它。Python是否会将其转换为如下的if语句,如果是,这种转换会在哪个阶段发生?

if condition:
    c = a
else:
    c = b

或者,三元运算符实际上是一种独立而单独的表达式,完全定义在不同的地方?如果是这样,我可以访问用于条件表达式的CPython代码吗?

我看过以下内容,它们解释了三元运算符的作用,但没有一个让我清楚地知道它们如何实现:


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

admin 更改状态以发布 2023年5月21日
0
0 Comments

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

这些看起来几乎相同,除了@L3viathan指出的JUMP_FORWARD的位置和一个额外的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

至于何时进行此转换,我假设是在"编译"为字节码期间的某个时候。

0
0 Comments

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中可以看到ast.IfExp()节点; 这个节点是专为条件表达式设计的。它具有testbodyorelse 部分,以表示组成条件的3个表达式、真和假部分。这在ast模块抽象语法部分有记录:

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

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

然后,将解析树编译为使用堆栈有条件地跳转到正确部分的字节码;我们可以直接将由ast.parse()生成的AST传递到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

如果条件为false,解释器会跳到指令8,否则会执行指令4和6,指令6跳到指令10(跳过else表达式)。最终结果是,指令4或指令8会将新的结果放在栈顶,供STORE_NAME将其移动到一个变量中。

if语句会生成一个不同的AST节点,生成的字节码也很相似,也会使用跳转。但是编译器将它们视为不同的语法部分,它必须这样。

表达式和语句是编程语言中两个非常不同的基本构建块。语句可以包含表达式,但表达式不能包含语句,只能包含其他表达式。表达式可以产生一个值(供周围的语法使用),但语句不能。因此,Python必须将条件表达式与语句区分开来,在语法分析器知道何时期望语句和何时允许表达式时。如果你将条件表达式转化为语句,你就不能将它作为更大的表达式的一部分使用!

因为if语句不是一条表达式,所以它不返回任何值(只有表达式才能产生值),因此生成的字节码也不会在栈顶产生值供周围的Python代码使用(没有c = if condition : ...)。if语句包含一个条件表达式和一个代码块,代码块必须始终包含更多的语句(有一种“表达式语句”可以让你在语句中只放一个表达式,例如1 + 1一行),这些语句可以“做些事情”,比如赋值或从函数返回,但它们所做的任何事情都不会使if返回某个值。

if语句的AST节点定义中可以看到这一点:

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

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

因此,这不仅适用于CPython,而且适用于所有Python实现。Python语法不是实现细节。

0