Python中如何实现三元运算符?
Python中如何实现三元运算符?
我理解在Python中,条件表达式(或三元运算符)是惰性的。它们表示的是条件执行而不是条件选择。换句话说,在下面的代码中,只有a
或b
中的一个会被评估:
c = a if condition else b
我感兴趣的是如何在内部实现它。Python是否会将其转换为如下的if
语句,如果是,这种转换会在哪个阶段发生?
if condition: c = a else: c = b
或者,三元运算符实际上是一种独立而单独的表达式,完全定义在不同的地方?如果是这样,我可以访问用于条件表达式的CPython代码吗?
我看过以下内容,它们解释了三元运算符的作用,但没有一个让我清楚地知道它们如何实现:
编辑:您可以假设CPython是参考实现。
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
至于何时进行此转换,我假设是在"编译"为字节码期间的某个时候。
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()
节点; 这个节点是专为条件表达式设计的。它具有test
、body
和 orelse
部分,以表示组成条件的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
是唯一的表达式节点,body
和orelse
都由零个或多个语句组成。 orelse
部分将保存任何elif ...:
测试作为更多的If()
节点,或任何其他类型的语句来形成无条件的else:
。通过零个或多个元素,您不能期望得到单个结果。
因此,这不仅适用于CPython,而且适用于所有Python实现。Python语法不是实现细节。