python连等赋值语句的执行逻辑
Python 中连等赋值语句的执行逻辑
问题
考虑如下python代码,思考程序的输出:
|
|
按照一般的逻辑,赋值语句从右向左计算,应先计算x[0]=1,其结果1再赋值给i,因此x将是[1,0,0]
,但是与实际的程序输出不符。
假设
在开始研究之前,可以先根据上述现象对python的连等赋值语句执行逻辑进行合理的假设,以便于后续设计验证。
序号 | 假设 |
---|---|
1 | 右向左结合,赋值表达式返回值(c语言逻辑);但已被实验证伪 |
2 | python自动假设赋值语句的依赖关系,按依赖顺序赋值 |
3 | 一条赋值语句拆开多句,按左到右顺序执行 |
验证
假设1
首先验证赋值语句是否有返回值,考虑如下测试程序:
|
|
可见python的赋值语句并不是表达式,并没有返回值。同时问题描述中的程序运行结果也不支持类似c等语言的连等逻辑。
因此python具有与其他语言不同的特性。
假设2
假设二是假设如果在一条连等语句中,不同的需要赋值的变量的表达式之间如果有相互引用,python会自动解析依赖关系,并确保被依赖的变量在最先赋值。
这个假设可以符合问题描述中的输出结果。但是为了进一步验证,考虑如下程序:
|
|
如果按照依赖推断的逻辑,第四行的赋值语句应当首先赋值i和j均为2,而后执行x[4]=2。注意列表的下标从0开始,所以期望的输出应当是[1,2,3,4,2]
,与实际的输出不符合。
假设3
假设三是假设连等赋值语句会被拆解成多个直接单一赋值的语句,例如问题描述中的i=x[i]=1
,会被拆解成:
|
|
这个假设也与问题描述相符。进一步考虑上面验证假设二时的程序,i=x[i+j]=j=2
将会被拆解成:
|
|
与程序的实际输出相符。
但是上述的验证方法都是类似“黑箱”的研究方法,只从python解释器的输入和输出推测其行为,作为问题的答案是不够严谨的。需要进一步研究。
解释
首先需要明确的是,python中的变量只是对实际值或者数据对象的引用,与c中的直接保存数据的方式不一样。
其次,python是解释性语言,在运行的过程中实时解释代码并执行。但是python在执行的过程中并不是直接执行源程序,而是将程序编译为具有单一操作的字节码,再由python的“虚拟机”执行。执行python脚本后出现的.pyc文件,就是对字节码的缓存。
既然python字节码一次只执行单一操作,那么对于我们研究上述问题显然是有帮助的。在python中,dis标准库提供了将字节码反汇编的功能,同时它也支持直接由python代码生成汇编指令。
例如,我们首先研究一个单独的普通赋值语句:
|
|
其中第一列表示字节码在源代码中的行号,第二列是字节码指令相对起始位置的偏移(字节)。第三列是指令名称,第四列是参数(猜测是在栈或者引用名称列表中的偏移量),第五列是参数以人类友好形式的表示。
为了理解上述反汇编后的代码,参考python的dis库中文官方文档
指令 | 名称 | 作用 |
---|---|---|
LOAD_CONST | 加载常数 | 将指定的常数引用推入栈顶压入 |
STORE_NAME | 引用分配 | 将栈顶的引用分配给变量 |
RETURN_VALUE | 返回值 | 将栈顶的引用返回 |
DUP_TOP | 复制引用 | 将栈顶的引用引用复制一份压入栈顶 |
因此可以看出,在执行x=1
这一语句的过程中,python解释器实际上完成的是如下的操作:
- 加载常数1的引用,压入系统栈顶
- 将栈顶的引用(即刚刚的常数1)分配引用给’x’这个变量名称
- 加载常数引用“None”,压入栈顶
- 将栈顶的引用(None)返回
从上面的操作也可以看出赋值语句确实没有返回值(None)。
下面来看连等的情况:
|
|
可以看出python在处理连等语句时,先载入要赋值的常数,而后不断复制引用-分配变量,并且是按表达式中从左到右的顺序。
现在可以来看问题描述中的连等语句,及其交换顺序后的代码的实际执行操作:
|
|
同样给出新出现的指令的解释:
指令 | 名称 | 作用 |
---|---|---|
LOAD_NAME | 加载引用 | 将某一变量名称对应的数据压入栈顶 |
STORE_SUBSCR | 列表赋值 | 要求栈顶第二个元素为列表,将列表中以栈顶元素为索引位置的数据赋值为栈顶第三个元素 |
因此上述指令中的第610、第48位置实际上就是执行了x[i]=1
的过程。但是由于连等语句中的顺序不同,在第一条中i先被分配了1这一数据的引用,而后才被作为索引,而在LOAD_NAME加载索引时,载入的已经是i新分配的值即1了(而非原来的0)。
在第二条交换了顺序后的赋值语句x[i]=i=1
中,i首先用作索引,而后才被分配新的引用,因此x[i]=x[0]=1
在i=1
之前执行。
小结
本文讨论python连等赋值语句的执行逻辑,首先按照黑箱方法提出可能的解释并进行简单检验,而后利用python中dis标准库的反汇编字节码实际验证连等语句的执行顺序,最后可以得出如下结论:
- python 的赋值语句不是表达式,没有返回值
- python中的连等赋值语句,可以拆分为多个单变量赋相同值的一组赋值语句,赋值顺序按变量在原语句中从左到右排列。
当然,利用反汇编字节码虽然能够较为完整地解释上述现象,但是有关python为何如此设计、python解释器在解释连等赋值语句时具体转换过程,就要涉及语法树、编译原理等更高层面的知识范畴了。