在Python中给切片元素赋值

7 投票
3 回答
2930 浏览
提问于 2025-04-16 06:18

这是一个关于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)

在这里,abi 都是表达式,而 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] 返回的是数据的一个副本,但内部的数据并没有被复制。

撰写回答