在Python中给切片元素赋值
这是一个关于Python如何处理数据和变量的简单问题。我做了很多实验,基本上搞懂了Python,但这个问题让我一直困惑:
[编辑:我把例子分开并重新排列,以便更清楚]
例子 1:
>>> a = [[1], 2]
>>> a[0:1]
[[1]]
>>> a[0:1] = [[5]]
>>> a
[[5], 2] # The assignment worked.
例子 2:
>>> a = [[1], 2]
>>> a[0:1][0]
[1]
>>> a[0:1][0] = [5]
>>> a
[[1], 2] # No change?
例子 3:
>>> a = [[1], 2]
>>> a[0:1][0][0]
1
>>> a[0:1][0][0] = 5
>>> a
[[5], 2] # Why now?
有没有人能给我解释一下这里发生了什么?
到目前为止,大家的回答似乎都在说 a[0:1]
返回一个新的列表,这个列表里包含了对 a
第一个元素的引用。但是我不明白这怎么能解释例子 1。
3 个回答
1
这里有三种不同的操作,都是通过方法调用来实现的:
a[i] = b
变成a.__setitem__(i, b)
del a[i]
变成a.__delitem__(i)
a[i]
当作表达式使用 变成a.__getitem__(i)
在这里,a
、b
和 i
都是表达式,而 i
可以包含用冒号简写语法创建的 切片对象。比如:
>>> class C(object):
... def __setitem__(self, *a):
... print a
...
>>> C()[1] = 0
(1, 0)
>>> C()['foo'] = 0
('foo', 0)
>>> C()['foo':'bar'] = 0
(slice('foo', 'bar', None), 0)
>>> C()['foo':'bar',5] = 0
((slice('foo', 'bar', None), 5), 0)
在你第三个例子中发生的事情是这样的:
a[0:1][0][0] = 5
变成
a.__getitem__(slice(0,1)).__getitem__(0).__setitem__(0, 5)
第一个 __getitem__
返回的是列表的一部分的副本,而第二个 __getitem__
返回的是里面的实际列表,然后这个列表通过 __setitem__
被修改。
而你的第二个例子则变成
a.__getitem__(slice(0,1)).__setitem__(0, 5)
所以 __setitem__
是在切片的副本上被调用的,这样原始列表就保持不变了。
3
我的理解是,切片操作会返回一个新的对象。也就是说,它的返回值是一个新的列表。
因此,你不能用赋值操作符来改变原始列表的值。
>>> a = [[1], 2, 3]
>>> k = a[0:2]
>>> id(a)
4299352904
>>> id(k)
4299353552
>>>
>>> id(a)
4299352904
>>> id(a[0:2])
4299352832
接下来再做一些相关的操作。
>>> k = 5
>>>
>>> id(k)
4298182344
>>> a[0] = [1,2]
>>> a
[[1, 2], 2, 3]
>>> id(a)
4299352904
>>>
[编辑:关于问题的第二部分]
>>> a[0:1] = [[5]]
下面的写法通常被称为切片赋值。对于内置的列表,这个操作是原子的,也就是说删除和插入会同时发生。我的理解是,这种操作不适用于自定义的序列。
7
a[0:1] 返回的是一个新数组,这个新数组里面包含了对数组 [1] 的引用,所以你通过这个引用修改了内部数组。
第一个情况之所以没有修改 [1] 数组,是因为你给复制的外部数组赋了一个新的内部数组值。
简单来说,a[0:1] 返回的是数据的一个副本,但内部的数据并没有被复制。