更新一个切片列表

15

我以为我懂Python的切片操作,但当我尝试更新一个被切片的列表时,我感到困惑:

>>> foo = [1, 2, 3, 4]
>>> foo[:1] = ['one'] # OK, foo updated
>>> foo
['one', 2, 3, 4] 
>>> foo[:][1] = 'two' # why foo not updated?
>>> foo
['one', 2, 3, 4] 
>>> foo[:][2:] = ['three', 'four'] # Again, foo not updated
>>> foo
['one', 2, 3, 4] 

为什么在foo[:][1] = 'two'之后,foo没有更新?

更新: 可能我没有清楚地解释我的问题。我知道切片时会创建一个新列表。我的疑问是为什么切片赋值会更新列表(例如foo[:1] = ['one']),但如果有两层切片,它不会更新原始列表(例如foo[:][2:] = ['three', 'four'])。


6
你之前使用过 numpy 吗?Numpy 数组在切片上与 Python 列表有所不同。 - Martijn Pieters
请阅读https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue。 - Dima Tisnek
恭喜!你已经发现了如何克隆或复制一个列表的方法!接下来问一下,这个复制有多深? - uhoh
4个回答

20

foo[:]foo 的一个副本。你改变了这个副本。


https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue 值得在这里补充一下,foo[:] 看起来可能相同,但它是 foo[:] = ... 中的左值,在 foo[:][2:] = ... 中则是右值。 - Dima Tisnek
2
@qarma,在Python中讲论lvalues和rvalues是不正确的。Python并不具备这些概念。在Python中,"[]="只是一个与"[]"不同的操作符。 - Jan Hudec
是的,那是正确的,请在答案中提供解释以供其他人查看。 - Dima Tisnek
@qarma:我觉得你把两个不同的人搞混了。Jan Hudec的回答已经解释了所有这些内容。 - ruakh
@qarma - 如果你有重要的贡献要做,为什么不创建自己的答案呢?没有必要在你的努力/想法中提及我的名字。 :) - TigerhawkT3
显示剩余2条评论

17

这是因为 Python 没有可以被赋值的 l-values,相反,一些表达式具有赋值形式,其不同于常规的赋值。

foo[something] 是以下语法糖:

foo.__getitem__(something)

但是foo[something] = bar实际上是一种不同的语法糖:

foo.__setitem__(something, bar)

其中,切片仅仅是 something 的一种特殊情况,因此 foo[x:y] 扩展为

foo.__getitem__(slice(x, y, None))

foo[x:y] = bar 扩展为:

foo.__setitem__(slice(x, y, None), bar)

现在使用切片的__getitem__返回一个新列表,它是指定范围的副本,因此修改它不会影响原始数组。赋值的工作原理是通过__setitem__成为一个不同的方法,并且可以简单地执行其他操作。

但是特殊的赋值处理仅适用于最外层操作。组成部分是普通表达式。因此,当您编写以下代码时:

foo[:][1] = 'two'

它会被扩展为

foo.__getitem__(slice(None, None, None)).__setitem__(1, 'two')

foo.__getitem__(slice(None, None, None)) 部分创建了一份副本,而该副本将被 __setitem__ 修改。但原始数组不会被修改。


但是为什么部分切片,比如foo[:1]=['one'],会起作用呢? - Liang
任何切片都可以。不过,双重切片就不行了。 - Jan Hudec
可能这也会有所帮助...... - Bhuro

13

这里需要注意的主要是 foo [:] 将返回它自己的一个副本,然后对返回的复制列表应用索引 [1]

# indexing is applied on copied list
(foo[:])[1] = 'two'
    ^
copied list

如果你保留对已复制列表的引用,就可以查看这个。因此,foo[:][1] = 'two' 操作可以重写为:

foo = [1, 2, 3, 4]

# the following is similar to foo[:][1] = 'two'

copy_foo = foo[:]  
copy_foo[1] = 'two'

现在,copy_foo已经被更改:

print(copy_foo)
# [1, 'two', 3, 4]

但是,foo保持不变:

print(foo)
# [1, 2, 3, 4]

在您的情况下,您没有为使用foo[:]复制foo列表时命名中间结果,也就是说,您没有保留对它的引用。在使用foo[:][1] = 'two'进行赋值之后,中间复制的列表将不再存在


3

使用

foo[1]  = 'two'

并且

foo[2:] = ['three', 'four']

并且它有效。

为什么的答案在上面的评论中(因为你正在使用副本)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接