元组解包顺序更改分配的值

hen*_*nry 48 python indexing list tuple-packing

我认为两者是相同的.

nums = [1, 2, 0]    
nums[nums[0]], nums[0] = nums[0], nums[nums[0]]    
print nums  # [2, 1, 0]

nums = [1, 2, 0]    
nums[0], nums[nums[0]] = nums[nums[0]], nums[0]    
print nums  # [2, 2, 1] 
Run Code Online (Sandbox Code Playgroud)

但结果却不同.
为什么结果不同?(为什么会产生第二个?)

Bha*_*Rao 46

先决条件 - 2个要点


介绍

当你执行a,b = c,d的值cd首次存储时.然后从左侧开始,首先将值a更改为c,然后将值b更改为d.

这里的问题是,如果b在改变值的同时对位置有任何副作用a,则将d其分配给后者 b,这是b受副作用的影响a.


用例

现在来解决你的问题

在第一种情况下,

nums = [1, 2, 0]    
nums[nums[0]], nums[0] = nums[0], nums[nums[0]]    
Run Code Online (Sandbox Code Playgroud)

nums[0]最初是1nums[nums[0]]2因为它的计算结果nums[1].因此1,2现在存储在内存中.

现在,元组拆包从左侧发生,所以

nums[nums[0]] = nums[1] = 1   # NO side Effect. 
nums[0] = 2
Run Code Online (Sandbox Code Playgroud)

因此print nums将打印[2, 1, 0]

但是在这种情况下

nums = [1, 2, 0]   
nums[0], nums[nums[0]] = nums[nums[0]], nums[0]    
Run Code Online (Sandbox Code Playgroud)

nums[nums[0]], nums[0] 就像第一种情况一样,将2,1放在堆栈上.

然而,在左侧,也就是说 nums[0], nums[nums[0]],改变nums[0]具有副作用,因为它被用作索引nums[nums[0]].从而

nums[0] = 2
nums[nums[0]] = nums[2] = 1  # NOTE THAT nums[0] HAS CHANGED
Run Code Online (Sandbox Code Playgroud)

nums[1]价值保持不变2.因此print nums将打印[2, 2, 1]


eph*_*eph 18

您可以定义一个类来跟踪该过程:

class MyList(list):
    def __getitem__(self, key):
        print('get ' + str(key))
        return super(MyList, self).__getitem__(key)
    def __setitem__(self, key, value):
        print('set ' + str(key) + ', ' + str(value))
        return super(MyList, self).__setitem__(key, value)
Run Code Online (Sandbox Code Playgroud)

对于第一种方法:

nums = MyList([1, 2, 0])
nums[nums[0]], nums[0] = nums[0], nums[nums[0]]
Run Code Online (Sandbox Code Playgroud)

输出是:

get 0
get 0
get 1
get 0
set 1, 1
set 0, 2
Run Code Online (Sandbox Code Playgroud)

而第二种方法:

nums = MyList([1, 2, 0])
nums[0], nums[nums[0]] = nums[nums[0]], nums[0]
Run Code Online (Sandbox Code Playgroud)

输出是:

get 0
get 1
get 0
set 0, 2
get 0
set 2, 1
Run Code Online (Sandbox Code Playgroud)

在这两种方法中,前三行与元组生成相关,而后三行与赋值相关.第一种方法的右侧元组是(1, 2)第二种方法(2, 1).

在分配阶段,第一种方法得到nums[0],其是1,和设置nums[1] = 1,然后nums[0] = 2,第二种方法分配nums[0] = 2,然后得到nums[0]其是2,最后设置nums[2] = 1.


Kas*_*mvd 10

这是因为python赋值优先级从左到右.所以在下面的代码中:

 nums = [1, 2, 0]
 nums[nums[0]], nums[0] = nums[0], nums[nums[0]]
Run Code Online (Sandbox Code Playgroud)

它首先分配了nums[0]to nums[nums[0]]means nums[1]==1,然后由于列表是可变对象,nums将是:

[1,1,0]
Run Code Online (Sandbox Code Playgroud)

然后nums[nums[0]]将分配给nums[0]哪些方式nums[0]==2和:

nums = [2,1,0]
Run Code Online (Sandbox Code Playgroud)

就像第二部分一样.

请注意,重要的一点是列表对象是可变的,当您在一段代码中更改它时,它可以就地更改.因此它会影响其余的代码.

评估订单

Python从左到右评估表达式.请注意,在评估分配时,右侧在左侧之前进行评估.