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

通过切片和for循环修改Python列表?,python,python-3.x,list,for-loop,slice,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

我试图通过切片和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]

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

当您在列表中迭代时,您将得到迭代生成的对象。由于
int
s在Python中是不可变的,因此不能更改
int
对象的状态。每次添加两个
int
s时,将创建一个新的
int
对象。因此,您的“for循环版本[which]无法修改列表”并没有真正尝试修改任何内容,因为它不会将添加的结果分配回列表

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


为了理解上一个(和第一个)示例,重要的是要知道切片会创建列表的部分副本(至少对于列表和元组,从技术上讲,您可以在自己的类中重写它)。正如您已经发现的那样,对这个新对象的任何更改都不会改变原始列表中的任何内容。

让我们将您的评论按1:1进行细分:

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

。它从
列表
对象调用该方法并将其调用。每次引用
x[:2]
时,都会创建一个新的切片对象(您可以简单地执行
id(x[:2])
,很明显,它一次都不会是相同的id)

2.)无法修改切片索引:

那不是真的。无法修改它,因为您是在
切片
实例上执行赋值,而不是在
列表
上执行赋值,因此它不会触发要在
列表
上执行的
\uuuuuuu setitem\uuuuuu
。而且,
int
是不可变的,因此它不能以任何方式更改

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

见上文。同样的原因-您正在分配一个切片实例,而不是直接修改
列表

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

此处引用的
z
x
元素中的实际对象。如果使用id(z)运行for循环,您会注意到它们与id(6)、id(7)、id(3)、id(4)、id(5)相同。尽管
list
包含所有5个相同的引用,但当您执行
z=…
操作时,您仅将新值分配给对象
z
,而不是存储在
list
中的对象。如果您想修改
列表
,您需要按索引分配它,因为您不能期望
1=6
x
变成
[6,2,3,4,5]

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

见上面我的答案。现在,您直接在
列表上执行项目分配,而不是在其表示上执行项目分配

6.)如果我将一个切片分配给一个变量,则可以对其进行修改:

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

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

当然
x
y
是两个不同的对象<代码>id(x)!=id(y)
,因此在
x
上执行的任何操作都不会影响
y
。但是,如果您分配了
y=x
,然后更改为
y
,则是,
x
也将受到影响

要在x:
中展开z的
,假设您有一个
类foo()
,并将这类的两个实例分配给列表
f

f1 = foo()
f2 = foo()
f = [f1, f2]
f
# [<__main__.foo at 0x15f4b898>, <__main__.foo at 0x15f4d3c8>]
f
仍然保持不变,因为foo实例保持不变,即使对象
f1
现在被分配到不同的值。出于同样的原因,每当您在x:
中为z更改
z
时,您只会影响对象
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
分配给新对象,
lst
将不会受到影响。

什么,您到底不明白的是什么?您期望的是什么?但本质上,切片创建副本,但切片分配会改变基础列表。由于
int
对象是不可变的,
z+=1
只是将一个新列表分配给变量
z
,因此它相当于
z=z+1
切片和切片分配之间存在差异。前者创建一个副本,后者进行一个特殊的调用。哇,我从没想过Python中的作业会这么复杂。感谢@Norrius的链接,这是非常有用的。在你的最后一点上,“每当你在
中为z在x:
中更改
z
,你只会影响对象
z
,但是列表中的任何内容都不会更改,直到你通过索引更新
x
”,如果z是一个可变对象,这仍然是真的吗?例如,如果x是一个列表列表,并且我在x:z.append(1)
中为z执行了
,那么应该修改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']]