python - Python中的三元运算符是如何实现的
问题描述
我知道条件表达式(或三元运算符)在 Python 中是惰性的。它们代表条件执行而不是条件选择。换句话说,只有一个a
或b
在以下情况下被评估:
c = a if condition else b
我很想知道这是如何在内部实现的。Python 是否转换if
为如下语句,如果是,这种转换发生在什么阶段?
if condition:
c = a
else:
c = b
或者三元运算符实际上是一个不同且独立的表达式,完全分开定义?如果是这样,我可以访问条件表达式的 CPython 代码吗?
我查看了以下内容,这些内容解释了三元运算符的作用,但没有一个说明它们是如何实现的:
编辑:您可以假设 CPython 参考实现。
解决方案
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.IfExp()
AST 中为分配生成的节点;这是条件表达式的专用节点。它有test
,body
和orelse
部分来表示构成条件的 3 个表达式,true 和 false 部分。这记录在ast
模块抽象语法部分中:
expr = [...] | [...] | IfExp(expr test, expr body, expr orelse)
这表明每个元素的类型是另一个expr
表达式节点。
然后解析树被编译成字节码,使用堆栈根据测试有条件地跳转到正确的部分;我们可以将生成的 ASTast.parse()
直接传递给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
因此,如果条件为假,解释器循环向前跳转到指令 8,否则执行指令 4 和 6,指令 6 向前跳转到指令 10(因此通过else
表达式)。最终结果是指令 4 或指令 8 将新结果放在堆栈顶部STORE_NAME
以移动到变量。
一条if
语句会产生不同的 AST 节点,而产生的字节码恰好非常相似,因为它也会使用跳转。但是编译器将它们视为不同的语法片段,而且它必须这样做。
表达式和语句是编程语言的两个非常不同的基本构建块。语句可以包含表达式,但表达式不能包含语句,只能包含其他表达式。并且表达式可以产生一个值(供周围的语法使用),但语句不能。因此 Python 必须以非常不同于语句的方式处理条件表达式,因为语法解析器知道何时期望语句以及何时允许表达式。如果将条件表达式转换为语句,则永远无法将这样的表达式用作更大表达式的一部分!
因为if
语句不是表达式,所以它不返回值(因为只有表达式才能产生值),因此生成的字节码不会在堆栈顶部产生值,以供周围的 Python 代码使用(没有c = if condition : ...
)。if
statements 包含一个condition expression和一个suite,它必须总是包含更多的 statements(有一个'表达式语句'这样的东西可以让你在一个语句中只放置一个表达式,例如1 + 1
在单行上),以及那些语句可以“做一些事情”,例如赋值或从函数返回,但它们所做的任何事情都不会if
返回某些东西。
这反映在if
语句的 AST 节点定义中:
stmt = [...] | [...] | If(expr test, stmt* body, stmt* orelse)
所以对于一个If
节点来说,test
是唯一的表达式节点,并且body
都由orelse
零个或多个语句组成。该orelse
部分将保存任何elif ...:
测试作为进一步If()
的节点,或任何其他类型的语句以形成无条件的else:
. 对于零个或多个元素,您不能期望一个结果。
所以这不是 CPython 独有的,它适用于所有 Python 实现。Python语法不是实现细节。
推荐阅读
- vue.js - Vuetify 2.x v-data-table 键盘命令/导航
- swift - Textfield 中的文本出现在文本字段的底部,即使在使用 .contentVerticalAlignment is .center 之后也是如此。这是特定于 iPhone
- laravel - 我使用“表字段/列”作为 varchar 类型而不是整数类型。但为什么要修剪第一个 0(零)?
- php - Woocommerce 增值税
- php - 在 Laravel 的周末(周六和周日)安排任务
- javascript - getPageNthWord 中的空字符串
- r - 根据绘图框值更新数据框表值
- android - Ajax API 调用不适用于cordova Android
- c++ - 如何在 Windows 7/8/10 中从 Qt 绑定连接/断开 USB 设备事件
- javascript - 我想从用户使用 javascript 填写的表单输入中动态更改按钮的值