Python字节码分析两值互换
在Python的语法中,可以在一行中不通过临时变量,即可交换变量。如:a, b = b, a
。
在直觉上,a, b = b, a
与b, a = a, b
得到的a, b值应当相等。但在实际中,如果a, b 在赋值过程中,存在引用关系的情况下,则得到值就会有差异,以下是一次字节码的详细分析。
-
假设有一个数组为
nums = [1, 2, 3, 4]
,此时执行nums[0], nums[1] = nums[1], nums[0]
后,不难得出nums变为[2, 1, 3, 4]
-
但如果执行
nums[nums[1]], nums[1] = nums[1], nums[nums[1]]
和nums[1], nums[nums[1]] = nums[nums[1]], nums[1]
, 此时nums的值将不太能确定。 -
以下使用Python3.8对
nums[0], nums[1] = nums[1], nums[0]
对执行过程进行分析1import dis 2 3def test(): 4 nums = [1, 2, 3, 4] 5 nums[0], nums[1] = nums[1], nums[0] 6 7dis.dis(test) 8 9# 得到nums[0], nums[1] = nums[1], nums[0]行的执行过程为 1024 LOAD_FAST 0 (nums) 1126 LOAD_CONST 1 (1) 1228 BINARY_SUBSCR 1330 LOAD_FAST 0 (nums) 1432 LOAD_CONST 5 (0) 1534 BINARY_SUBSCR 1636 ROT_TWO 1738 LOAD_FAST 0 (nums) 1840 LOAD_CONST 5 (0) 1942 STORE_SUBSCR 2044 LOAD_FAST 0 (nums) 2146 LOAD_CONST 1 (1) 2248 STORE_SUBSCR 2350 LOAD_CONST 0 (None) 2452 RETURN_VALUE 25 26# co_consts 27(None, 1, 2, 3, 4, 0) 28 29# co_varnames 30('nums',)
字节码说明
code DESC LOAD_FAST 将指向局部对象 co_varnames[var_num]
的引用推入栈顶。LOAD_CONST 将 co_consts[consti]
推入栈顶。BINARY_SUBSCR 先POP得到k值,再对栈顶执行 d = d[k] ROT_TWO 交换两个最顶层的堆栈项。 STORE_SUBSCR 实现 TOS1[TOS] = TOS2
。再将栈顶头三个移出RETURN_VALUE 返回 TOS 到函数的调用者。 得出流程图为
可以看出,在等号两边执行时,都为从左到右执行。
因此分析以下赋值结果
nums = [1, 2, 3, 4] nums[nums[1]], nums[1] = nums[1], nums[nums[1]] nums[nums[1]], nums[1] = 2, 3 nums[2], nums[1] = 2, 3 nums = [1, 3, 2, 4]
nums = [1, 2, 3, 4] nums[1], nums[nums[1]] = nums[nums[1]], nums[1] nums[1], nums[nums[1]] = 3, 2 nums[1] = 3 nums[3] = 2 nums = [1, 3, 3, 2]