Python中的三元运算符是如何实现的
How is ternary operator implemented in Python
我知道条件表达式(或三元运算符)在 Python 中是惰性的。它们代表条件执行而不是条件选择。换句话说,只有 a
或 b
中的一个被评估如下:
c = a if condition else b
我想知道的是如何这是内部实施的。 Python 是否转换为如下所示的 if
语句?如果是,此转换发生在哪个阶段?
if condition:
c = a
else:
c = b
或者三元运算符实际上是一个不同的独立表达式,完全单独定义?如果是这样,我可以访问条件表达式的 CPython 代码吗?
我看过以下解释什么三元运算符的作用,但none其中清楚如何 已实施:
- 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 参考实现。
Python 不需要转换任何东西,如果需要也不能。
条件表达式使用language grammar into an abstract syntax tree, which in turn is then compiled to bytecode. You can produce the AST by using the ast.parse()
function解析:
>>> 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
module Abstract Grammar section:
expr = [...]
| [...]
| IfExp(expr test, expr body, expr orelse)
这表明每个元素的类型是另一个expr
表达式节点。
然后将解析树编译为字节码,使用堆栈根据测试有条件地跳转到正确的部分;我们可以将 ast.parse()
生成的 AST 直接传递给 compile()
function, after which the 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
语句 不是表达式 ,所以它 return 不是一个值 (因为只有表达式可以产生一个值),因此生成的字节码不会在堆栈顶部产生一个值供周围的 Python 代码使用(没有 c = if condition : ...
)。 if
语句包含一个 条件表达式 和一个 套件 ,它必须总是由更多的语句组成(有一个'expression statement' 让你只在语句中放置一个表达式,例如 1 + 1
在一行中),并且这些语句可以 'do stuff' 像赋值或函数中的 return ,但他们所做的任何事情都不会 if
return 有所作为。
这反映在 if
语句的 AST 节点定义中:
stmt = [...]
| [...]
| If(expr test, stmt* body, stmt* orelse)
所以对于If
节点,test
是唯一的表达式节点,body
和orelse
都由零个或多个 语句。 orelse
部分将包含任何 elif ...:
测试作为进一步的 If()
节点,或任何其他类型的语句以形成无条件 else:
。对于零个或多个元素,您不能期待一个结果。
所以这不是 CPython 独有的,这适用于所有 Python 实现。 Python 语法 不是实现细节。
Does Python convert to an if statement as below
差不多。
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
至于什么时候这种转换发生,我会假设在"compilation"到字节码的某个时间。
什么
如果您要问什么,那么为了最好地理解它,您需要了解功能性和程序性之间的区别。一个可以转换为另一个,但两者都可以独立查看,您无需将一个转换为另一个即可理解它们。
value_a if condition else value_b
是函数式的,returns 是一个值 value_a
或 value_b
.
if condition then:
do_a
else:
do_b
是程序性的,它确实 do_a
或 do_b
。
注意:程序就是做,做这个,然后做那个,或者那个。功能是关于价值的,是这个还是那个。
如何
如果您要问如何实现,那么您将需要查看其中一个实现的源代码。请注意,只要行为正确,每个实现不必以相同的方式进行。
我知道条件表达式(或三元运算符)在 Python 中是惰性的。它们代表条件执行而不是条件选择。换句话说,只有 a
或 b
中的一个被评估如下:
c = a if condition else b
我想知道的是如何这是内部实施的。 Python 是否转换为如下所示的 if
语句?如果是,此转换发生在哪个阶段?
if condition:
c = a
else:
c = b
或者三元运算符实际上是一个不同的独立表达式,完全单独定义?如果是这样,我可以访问条件表达式的 CPython 代码吗?
我看过以下解释什么三元运算符的作用,但none其中清楚如何 已实施:
- 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 参考实现。
Python 不需要转换任何东西,如果需要也不能。
条件表达式使用language grammar into an abstract syntax tree, which in turn is then compiled to bytecode. You can produce the AST by using the ast.parse()
function解析:
>>> 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
module Abstract Grammar section:
expr = [...] | [...] | IfExp(expr test, expr body, expr orelse)
这表明每个元素的类型是另一个expr
表达式节点。
然后将解析树编译为字节码,使用堆栈根据测试有条件地跳转到正确的部分;我们可以将 ast.parse()
生成的 AST 直接传递给 compile()
function, after which the 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
语句 不是表达式 ,所以它 return 不是一个值 (因为只有表达式可以产生一个值),因此生成的字节码不会在堆栈顶部产生一个值供周围的 Python 代码使用(没有 c = if condition : ...
)。 if
语句包含一个 条件表达式 和一个 套件 ,它必须总是由更多的语句组成(有一个'expression statement' 让你只在语句中放置一个表达式,例如 1 + 1
在一行中),并且这些语句可以 'do stuff' 像赋值或函数中的 return ,但他们所做的任何事情都不会 if
return 有所作为。
这反映在 if
语句的 AST 节点定义中:
stmt = [...] | [...] | If(expr test, stmt* body, stmt* orelse)
所以对于If
节点,test
是唯一的表达式节点,body
和orelse
都由零个或多个 语句。 orelse
部分将包含任何 elif ...:
测试作为进一步的 If()
节点,或任何其他类型的语句以形成无条件 else:
。对于零个或多个元素,您不能期待一个结果。
所以这不是 CPython 独有的,这适用于所有 Python 实现。 Python 语法 不是实现细节。
Does Python convert to an if statement as below
差不多。
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
至于什么时候这种转换发生,我会假设在"compilation"到字节码的某个时间。
什么
如果您要问什么,那么为了最好地理解它,您需要了解功能性和程序性之间的区别。一个可以转换为另一个,但两者都可以独立查看,您无需将一个转换为另一个即可理解它们。
value_a if condition else value_b
是函数式的,returns 是一个值 value_a
或 value_b
.
if condition then:
do_a
else:
do_b
是程序性的,它确实 do_a
或 do_b
。
注意:程序就是做,做这个,然后做那个,或者那个。功能是关于价值的,是这个还是那个。
如何
如果您要问如何实现,那么您将需要查看其中一个实现的源代码。请注意,只要行为正确,每个实现不必以相同的方式进行。