Python字节码分析两值互换

在Python的语法中,可以在一行中不通过临时变量,即可交换变量。如:a, b = b, a

在直觉上,a, b = b, ab, 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]对执行过程进行分析

    import dis
    
    def test():
        nums = [1, 2, 3, 4]
        nums[0], nums[1] = nums[1], nums[0]
    
    dis.dis(test)
    
    # 得到nums[0], nums[1] = nums[1], nums[0]行的执行过程为
    24 LOAD_FAST                0 (nums)
    26 LOAD_CONST               1 (1)
    28 BINARY_SUBSCR
    30 LOAD_FAST                0 (nums)
    32 LOAD_CONST               5 (0)
    34 BINARY_SUBSCR
    36 ROT_TWO
    38 LOAD_FAST                0 (nums)
    40 LOAD_CONST               5 (0)
    42 STORE_SUBSCR
    44 LOAD_FAST                0 (nums)
    46 LOAD_CONST               1 (1)
    48 STORE_SUBSCR
    50 LOAD_CONST               0 (None)
    52 RETURN_VALUE
    
    # co_consts
    (None, 1, 2, 3, 4, 0)
    
    # co_varnames
    ('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 到函数的调用者。

    得出流程图为

    code

    可以看出,在等号两边执行时,都为从左到右执行。

    因此分析以下赋值结果

    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]

    
    
    
    
    
    

```