通过切片和for循环修改Python列表?

时间:2018-02-07 17:02:57

标签: python python-3.x list for-loop slice

我试图通过切片和for循环来修改列表中的值,并遇到了一些非常有趣的行为。如果有人能在这里解释内部发生的事情,我将不胜感激。

>>> x = [1,2,3,4,5]
>>> x[:2] = [6,7] #slices can be modified
>>> x
[6, 7, 3, 4, 5]
>>> x[:2][0] = 8 #indices of slices cannot be modified
>>> x
[6, 7, 3, 4, 5]
>>> x[:2][:1] = [8] #slices of slices cannot be modified
>>> x
[6, 7, 3, 4, 5]
>>> for z in x: #this version of a for-loop cannot modify lists
...    z += 1
... 
>>> x
[6, 7, 3, 4, 5]
>>> for i in range(len(x)): #this version of a for-loop can modify lists
...    x[i] += 1
... 
>>> x
[7, 8, 4, 5, 6]
>>> y = x[:2] #if I assign a slice to a var, it can be modified...
>>> y[0] = 1
>>> y
[1, 8]
>>> x #...but it has no impact on the original list
[7, 8, 4, 5, 6]

2 个答案:

答案 0 :(得分:2)

让我们逐一打破你的评论:

1。)x[:2] = [6, 7]切片可以修改:

See these answers here。它会从list对象和__setitem__调用assigning the slice方法。每次引用x[:2]时,都会创建一个新的切片对象(您可以简单地执行id(x[:2])并且它很明显,而不是一次是相同的ID)。

2。)切片索引不能修改:

那不是真的。它无法修改,因为您在slice实例上执行了分配,而不是list,因此它不会触发__setitem__list上执行。此外,int是不可变的,因此不能以任何方式进行更改。

3.)切片切片无法修改:

见上文。同样的原因 - 您正在分配切片的实例而不直接修改list

4.。)此版本的for循环无法修改列表:

这里引用的{p> zx元素中的实际对象。如果您使用id(z)运行for循环,则请注意它们与id(6), id(7), id(3), id(4), id(5)相同。即使list包含所有5个相同的引用,但当您执行z = ...时,您只是将新值分配给对象z,而不是list中存储的对象。如果您要修改list,则需要按索引进行分配,出于同样的原因,您不能指望1 = 6x转换为{{ 1}}。

5.。)这个版本的for循环可以修改列表:

请参阅上面的答案。现在,您将直接在[6, 2, 3, 4, 5]上执行项目分配,而不是其表示。

6。)如果我将一个切片分配给一个var,它可以被修改:

如果您一直关注到目前为止,您现在意识到您正在将list的实例分配给对象x[:2],该对象现在是y 。故事如下 - 您在list上按索引执行项目分配,当然会更新。

7。)...但它对原始列表没有影响:

当然。 yx是两个不同的对象。 y,因此对id(x) != id(y)执行的任何操作都不会影响x。如果您已分配y然后对y = x进行了更改,那么是的,y也会受到影响。

要在x上展开一点,假设您有一个for z in x:,并为列表class foo()分配了两个这样的实例:

f

请注意,相关引用是实际的f1 = foo() f2 = foo() f = [f1, f2] f # [<__main__.foo at 0x15f4b898>, <__main__.foo at 0x15f4d3c8>] 实例,而不是对象foof1。即使我做了以下事情:

f2

f1 = 'hello' f # [<__main__.foo at 0x15f4b898>, <__main__.foo at 0x15f4d3c8>] 仍保持不变,因为即使对象f现在被分配给不同的值,foo实例仍保持不变。出于同样的原因,每当您对f1中的z进行更改时,您只会影响对象for z in x:,但在更新z之前,列表中的任何内容都不会更改索引。

但是,如果对象具有属性或可变,则可以直接更新循环中引用的对象:

x

这是因为您直接更新引用中的对象而不是分配给对象x = ['foo'] y = ['foo'] lst = [x,y] lst # [['foo'], ['foo']] for z in lst: z.append('bar') lst # [['foo', 'bar'], ['foo', 'bar']] x.append('something') lst # [['foo', 'bar', 'something'], ['foo', 'bar']] 。但是,如果您将zx分配给新对象,y将不会受到影响。

答案 1 :(得分:0)

这里没有什么奇怪的事情发生。从列表中获取的任何切片都是包含原始列表副本的新对象。元组也是如此。

当您遍历列表时,您将获得迭代产生的对象。由于int在Python中是不可变的,因此您无法更改int个对象的状态。每次添加两个int时,都会创建一个新的int对象。所以你的&#34;版本的for循环[哪个]不能修改列表&#34;并没有真正尝试修改任何东西,因为它不会将添加的结果分配回列表。

也许你现在可以猜到为什么你的第二种方法是不同的。它使用特殊的切片语法,它不会真正创建列表的切片,并允许您分配到列表(documentation)。添加操作创建的新创建的对象通过此方法存储在列表中。

为了理解你的上一个(和你的第一个)例子,重要的是要知道切片创建(至少对于列表和元组,技术上你可以在你自己的类中覆盖它)你的列表的部分副本。如您所知,对此新对象的任何更改都不会更改原始列表中的任何内容。