Python 元组解包顺序更改指定的值

Python 元组解包顺序更改指定的值,python,list,indexing,tuple-packing,Python,List,Indexing,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] 但是结果不同。 为什么结果不同?(为什么会出现第二个?先决条件-2个要点 列表是可变

我认为两者是相同的

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] 
但是结果不同。
为什么结果不同?(为什么会出现第二个?

先决条件-2个要点
  • 列表是可变的

    列表的主要部分是列表是可变的。这意味着 列表的值可以更改。这就是你为什么这么做的原因之一 面对麻烦

  • 评估顺序

    另一部分是在解包元组时,开始计算 从左到右


介绍 执行
a,b=c,d
时,首先存储
c
d
的值。然后从左侧开始,首先将
a
的值更改为
c
,然后将
b
的值更改为
d

这里的问题是,如果在更改
a
的值时
b
的位置有任何副作用,则
d
被分配给后面的
b
,这是受
a
副作用影响的
b


用例 现在来谈谈你的问题

在第一种情况下

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

现在元组解包发生在左手边,所以

nums[nums[0]] = nums[1] = 1   # NO side Effect. 
nums[0] = 2
因此,
print nums
将打印
[2,1,0]

但是在这种情况下

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

然而,在左侧,即
nums[0],nums[nums[0]]
,更改
nums[0]
具有副作用,因为它被用作
nums[0]
中的索引。因此

nums[0] = 2
nums[nums[0]] = nums[2] = 1  # NOTE THAT nums[0] HAS CHANGED

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

这是因为python的分配优先级是从左到右的。因此在以下代码中:

 nums = [1, 2, 0]
 nums[nums[0]], nums[0] = nums[0], nums[nums[0]]
它首先将
nums[0]
分配给
nums[nums[0]]
意味着
nums[1]==1
,然后由于列表是可变对象,nums将是:

[1,1,0]
然后将
nums[nums[0]]
分配给
nums[0]
,这意味着
nums[0]==2
,并且:

nums = [2,1,0]
第二部分也是如此

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

Python从左到右计算表达式。请注意,在计算赋值时,右侧的求值先于左侧


在第一个示例中,发生的情况是nums[1]被设置为1,然后nums[0]被设置为2,正如您所期望的那样


在第二个示例中,nums[0]设置为2,然后nums[2]设置为1。这是因为,在本例中,当赋值发生时,左侧nums[nums[0]]实际上是在引用nums[2],因为nums[0]刚刚被设置为2。

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

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)
对于第一种方法:

nums = MyList([1, 2, 0])
nums[nums[0]], nums[0] = nums[0], nums[nums[0]]
输出为:

get 0
get 0
get 1
get 0
set 1, 1
set 0, 2
get 0
get 1
get 0
set 0, 2
get 0
set 2, 1
而第二种方法:

nums = MyList([1, 2, 0])
nums[0], nums[nums[0]] = nums[nums[0]], nums[0]
输出为:

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


在赋值阶段,第一种方法得到
nums[0]
,即
1
,然后设置
nums[1]=1
,然后
nums[0]=2
,第二种方法赋值
nums[0]=2
,然后得到
nums[0]
,即
2
,最后设置
nums[2]=1

这都是关于列表的求值顺序和易变性…好问题@Henry这个问题把我绊倒了。我真的认为这些说法也是一样的。这是一个很好的例子,说明了在计算语句和可变列表时,您需要知道操作的顺序。谢谢你的提问!相关:如果设计是为了
a[b],c[d]=e,f
按顺序计算a,b,c,d,e和f,然后赋值,那么像这样的行会工作得更好,但是现在更改东西已经太晚了。@user2357112是的,我想他们不会做这些更改,因为他们不想破坏现在的东西。我们需要等到python4发布另一个向后不兼容的产品。