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

Posted

技术标签:

【中文标题】元组拆包顺序更改分配的值【英文标题】:Tuple unpacking order changes values assigned 【发布时间】:2016-03-14 06:51:23 【问题描述】:

我认为两者是相同的。

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] 

但结果不同。 为什么结果不一样? (为什么是第二个结果?)

【问题讨论】:

这都是关于列表的评估顺序和可变性...好问题@henry 这个问题让我大吃一惊。老实说,我认为这些陈述也是一样的。这是一个很好的例子,说明在评估可变语句和列表时如何需要知道操作顺序。感谢您提出问题! 相关:Multiple assignment and evaluation order in Python - Stack Overflow 【参考方案1】:

先决条件 - 2 个要点

列表是可变的

列表的主要部分是列表是可变的。这意味着 列表的值可以更改。这就是你的原因之一 面对麻烦。 Refer the docs for more info

评估顺序

另一部分是在解包元组时,评估开始 从左到右。 Refer the docs for more info


简介

当您执行a,b = c,d 时,首先存储cd 的值。然后从左边开始,先将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[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]

【讨论】:

如果设计是让a[b], c[d] = e, f 按此顺序评估 a、b、c、d、e 和 f 并且 then i> 分配,但现在改变已经太迟了。 @user2357112 是的,我猜他们没有做出这些改变是因为他们不想破坏现有的东西。我们需要等到 python4 发布另一个向后不兼容的产品。【参考方案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

而第二种方法:

nums = MyList([1, 2, 0])
nums[0], nums[nums[0]] = nums[nums[0]], nums[0]

输出是:

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

【讨论】:

【参考方案3】:

这是因为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]

第二部分也是如此。

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

Evaluation order

Python 从左到右计算表达式。请注意,在评估分配时,右侧会先于左侧进行评估。

【讨论】:

【参考方案4】:

在第一个示例中,发生的情况是 nums[1] 设置为 1,然后 nums[0] 设置为 2,如您所料。

在第二个示例中,nums[0] 设置为 2,然后 nums[2] 设置为 1。这是因为在这种情况下,左侧的 nums[nums [0]] 在分配发生时实际上是在引用 nums[2],因为 nums[0] 刚刚被设置为 2。

【讨论】:

以上是关于元组拆包顺序更改分配的值的主要内容,如果未能解决你的问题,请参考以下文章

元组拆包具名元祖

什么是方案相当于元组拆包?

元组拆包与普通赋值有何不同? [复制]

元组--补充

元组--补充

元组学习