我试图通过切片和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]
答案 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>z
是x
元素中的实际对象。如果您使用id(z)运行for循环,则请注意它们与id(6), id(7), id(3), id(4), id(5)
相同。即使list
包含所有5个相同的引用,但当您执行z = ...
时,您只是将新值分配给对象z
,而不是list
中存储的对象。如果您要修改list
,则需要按索引进行分配,出于同样的原因,您不能指望1 = 6
将x
转换为{{ 1}}。
5.。)这个版本的for循环可以修改列表:
请参阅上面的答案。现在,您将直接在[6, 2, 3, 4, 5]
上执行项目分配,而不是其表示。
6。)如果我将一个切片分配给一个var,它可以被修改:
如果您一直关注到目前为止,您现在意识到您正在将list
的实例分配给对象x[:2]
,该对象现在是y
。故事如下 - 您在list
上按索引执行项目分配,当然会更新。
7。)...但它对原始列表没有影响:
当然。 y
和x
是两个不同的对象。 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>]
实例,而不是对象foo
和f1
。即使我做了以下事情:
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']]
。但是,如果您将z
或x
分配给新对象,y
将不会受到影响。
答案 1 :(得分:0)
这里没有什么奇怪的事情发生。从列表中获取的任何切片都是包含原始列表副本的新对象。元组也是如此。
当您遍历列表时,您将获得迭代产生的对象。由于int
在Python中是不可变的,因此您无法更改int
个对象的状态。每次添加两个int
时,都会创建一个新的int
对象。所以你的&#34;版本的for循环[哪个]不能修改列表&#34;并没有真正尝试修改任何东西,因为它不会将添加的结果分配回列表。
也许你现在可以猜到为什么你的第二种方法是不同的。它使用特殊的切片语法,它不会真正创建列表的切片,并允许您分配到列表(documentation)。添加操作创建的新创建的对象通过此方法存储在列表中。
为了理解你的上一个(和你的第一个)例子,重要的是要知道切片创建(至少对于列表和元组,技术上你可以在你自己的类中覆盖它)你的列表的部分副本。如您所知,对此新对象的任何更改都不会更改原始列表中的任何内容。