同时分配索引 Python 中的不同列表元素

Simultaneous assignment indexing different list elements in Python

>>arr = [4, 2, 1, 3]
>>arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]
>>arr

我期望的结果 >>[3, 2, 1, 4]

我得到的结果 >>[3, 2, 4, 3]

基本上我正在尝试交换#4 和#3(在我的实际问题中,索引不会是 0,而是迭代器“i”。所以我不能这样做 arr[0], arr[3] = arr[3], arr[0]) 我以为我对同时分配相当了解。显然我错了。我不明白为什么赋值左侧的 arr[arr[0]-1] 评估为 arr[2] 而不是 arr[3]。如果赋值同时发生(从右边计算),

arr[0](在左侧第二个元素的索引内​​)仍应为“4”

arr[0] -1(左边第二个元素的索引)因此应该是“3”

因为目标列表没有得到同时的评估。 Here is the relevant section of the docs:

The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets.

有两点要记住,右边首先计算表达式。所以在 RHS 上,我们首先创建元组:

 (3, 4)

注意,从左到右完成了。现在,左边的目标列表中的每个目标的分配是按顺序完成的:

arr[0] = 3

那么下一个目标,arr[0]3,3-1是2

arr[2] = 4

所以一个简单的解决方案是在交换之前先计算索引:

>>> arr = [4, 2, 1, 3]
>>> i, j = arr[0] - 1, 0
>>> arr[j], arr[i] = arr[i], arr[j]
>>> arr
[3, 2, 1, 4]

这是一个使用我们可以轻松定义的冗长列表的演示:

>>> class NoisyList(list):
...     def __getitem__(self, item):
...         value = super().__getitem__(item)
...         print("GETTING", item, "value of", value)
...         return value
...     def __setitem__(self, item, value):
...         print("SETTING", item, 'with', value)
...         super().__setitem__(item, value)
...
>>> arr = NoisyList([4, 2, 1, 3])
>>> arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]
GETTING 0 value of 4
GETTING 3 value of 3
GETTING 0 value of 4
SETTING 0 with 3
GETTING 0 value of 3
SETTING 2 with 4

好的,发生这种情况是因为当您将 arr[arr[0]-1] 分配给它时 arr[0] 更改为 3。在那之后,当 python 查看 arr[arr[0]-1] 并尝试将 arr[0] 的值分配给它时,如果发现 arr[0]3 因为在之前你已经改变了它的分配。一个小演示:

arr = [4, 2, 1, 3]

arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]
 │                           │ 
 └──────────┬────────────────┘
            │
            │
      these are done first 
      and the list becomes:
      [3, 2, 1, 3]

Next when the python takes a look at
                     these two, it:
                         │
            ┌────────────┴───────────────┐
            │                            │ 
arr[0], arr[arr[0]-1] = arr[arr[0]-1], arr[0]

it finds the `arr[0]` to be `3` so, it assigns `3` to the `3rd` 
element because `arr[arr[0]-1]` is 3.

两个值的替换并不是真正同时的;它们按从左到右的顺序处理。所以在那个过程中改变 arr 会导致这种行为。

考虑这个替代示例:

>>> arr = [1, 2, 3]
>>> arr[0], arr[arr[0]] = 10, 5
...

假设同时重新分配,我们尝试用 10 替换 arr 的第一个值,然后 arr[0]th (又名 1st)具有 5 的元素。所以希望,我们得到[10, 5, 3]。但这失败了 IndexError: list assignment index out of range。如果您在出现此错误后检查 arr

>>> arr
[10, 2, 3]

第一次任务完成,第二次失败。当涉及到第二个赋值时(在逗号之后),实际的 arr[0]th(又名 10th)找不到值(b/c 列表不那么长)。

通过明确指定第二个赋值失败(仍然通过指定超出范围的索引)也可以看到此行为:

>>> arr = [1, 2, 3]
>>> arr[0], arr[99] = 5, 6
# Same index error, but arr becomes [5, 2, 3]

这让人想起 modifying a list you are iterating over,这有时是可行的,但常常令人气馁,因为它会导致您所看到的问题。

一种替代方法是创建一个副本(这有时是一种修改您正在迭代的列表的解决方案),并使用它来引用 arr:

中的值
>>> arr = [4, 2, 1, 3]
>>> copy = arr.copy()
>>> arr[0], arr[copy[0]-1] = copy[copy[0]-1], copy[0]
>>> arr
[3, 2, 1, 4]

虽然这里很丑。 is much nicer, or this idiomatic approach 中的替代方案应该也可以工作!