如何在列表切片中进行赋值?

137

Python文档指出切片一个列表会返回一个新的列表。
现在,如果返回一个“新”的列表,那么我对“切片赋值”有以下问题:

a = [1, 2, 3]
a[0:2] = [4, 5]
print a

现在的输出将会是:

[4, 5, 3] 
  1. 返回值的函数怎么可能出现在表达式的左边?
  2. 是的,我读过文档,它说这是可能的,但既然切片一个列表会返回一个“新”的列表,那为什么原始列表被修改了呢?我无法理解它背后的机制。

@Mark Longair 抱歉,我以为只有代码应该被格式化,而不是输出。 - Kartik Anand
3
请参见:6.2 赋值语句 - Josh Lee
是的,我确实理解赋值,但是新列表在切片时生成的概念让我很烦。 - Kartik Anand
1
@KartikAnand 切片赋值是一种特殊情况,其中不会创建新列表。在“=”左侧没有名称绑定的情况下创建对象是没有意义的,因此Python将其转换为更符合预期的形式,而不是将其丢弃为无效语法。由于Python没有引用,因此切片的结果不能更改原始列表。你得到了一个副本。如果您提供有关应用程序的更多信息,我们可能能够更好地帮助您以“Pythonic”的方式完成任务。 :) - Casey Kuball
1
@Darthfett 我现在没有在开发任何应用程序,我是在自学Python,在开始动手之前先熟悉一下 :) - Kartik Anand
5个回答

156
你混淆了两个使用非常相似语法的不同操作: 1) 切片操作:
b = a[0:2]

这将复制 a 切片并将其赋值给 b

2) 切片赋值:

a[0:2] = b

这将用b的内容替换a的切片。

尽管语法相似(我猜是有意为之!),但这是两个不同的操作。


8
第二种情况下,为什么a的切片不是一个新列表,这就是我的疑问。 - Kartik Anand
17
因为它不是这样的。这不是语言规范所指定的内容。 - Marcin
4
基本上是这样的。口译员知道哪个是哪个,并适当地处理它们。 - NPE
有没有办法引用列表的一部分(即切片,而不是切片赋值),而不必在内存中创建一个全新的列表副本?如果我有一个(非常)长的列表,并想循环遍历除第一个元素外的所有元素,我的直觉是 for i in list[1:]:,但我不想复制任何内存。我该怎么做? - Dubslow
2
@Dubslow:你可以使用itertools模块来实现。对于你的情况,使用函数islice,并将start=1stop=None。这样可以避免任何副本,并使用惰性评估(在你的情况下是对原始列表的惰性访问)。 - Spiros
显示剩余4条评论

92
当你在 = 操作符的左侧指定 a 时,你使用的是 Python 的 普通赋值,它更改了当前上下文中名称 a 指向的新值。这不会更改 a 原来所指向的值。
当你在 = 操作符的左侧指定 a[0:2] 时,你告诉 Python 你想使用 切片赋值。切片赋值是一个特殊的列表语法,可以插入、删除或替换列表内容: 插入:
>>> a = [1, 2, 3]
>>> a[0:0] = [-3, -2, -1, 0]
>>> a
[-3, -2, -1, 0, 1, 2, 3]

删除:

>>> a
[-3, -2, -1, 0, 1, 2, 3]
>>> a[2:4] = []
>>> a
[-3, -2, 1, 2, 3]

替换:

>>> a
[-3, -2, 1, 2, 3]
>>> a[:] = [1, 2, 3]
>>> a
[1, 2, 3]

注意:

切片的长度可能与分配的序列的长度不同,因此如果目标序列允许,则会更改目标序列的长度。- 来源

切片赋值提供了类似于元组拆包的功能。例如,a[0:1] = [4, 5]等同于:

# Tuple Unpacking
a[0], a[1] = [4, 5]

通过元组解包,你可以修改非连续的列表:

>>> a
[4, 5, 3]
>>> a[-1], a[0] = [7, 3]
>>> a
[3, 5, 7]

然而,元组解包只限于替换,你不能插入或删除元素。

在所有这些操作之前和之后,a 都是完全相同的列表。Python只是提供了一种简洁的语法来原地修改列表。


7
相似但不完全相同,因为左右两边的元素数量可以不相等。 - Mark Ransom
@MarkRansom 这是一个很好的观点,我已经添加了更多信息以使其更加明显。 - Casey Kuball
2
a[:] = some_lista = some_list[:] 或者 a = some_list 是等价的吗? - jadkik94
2
@jadkik94 不是这样的。a[:] = some_lista 的每个元素设置为 some_list 中的元素。执行你提到的任何一种方法都会改变 a 的值。例如:a = [1, 2, 3]b = aa[:] = [4, 5, 6]a is b。如果它改变了 a 的值而不是突变它,最后一行将为 False。 - Casey Kuball
@CaseyKuball 当我们进行切片赋值时,start: stop: step 的值是否总是应该为正数? - strikersps
@strikersps,我从未遇到过这样的限制。您可以像正数一样使用负索引来引用列表的末尾。但是,有一个限制,即您无法从末尾“包裹”到开头。经过实验,a[-5:3] = list(range(8))似乎会将值列表插入到a[-5],而不是替换a[-5:]a[:3]。但是,您可以同时使用负起始和停止以及负步长。 - Casey Kuball

37

我之前也遇到过同样的问题,与语言规范有关。根据赋值语句

  1. 如果赋值操作的左侧是下标,Python会在该对象上调用__setitem__方法。 a[i] = x等价于a.__setitem__(i,x)

  2. 如果赋值操作的左侧是切片,Python也会调用__setitem__,但参数不同: a[1:4]=[1,2,3]等同于 a.__setitem__(slice(1,4,None), [1,2,3])

这就是为什么在'='左侧的列表切片行为不同的原因。


5
通过在赋值操作左侧进行切片,您可以指定要分配的项目。

2
当你执行 a[0:2] = [4,5] 时,你将等号左边的部分(切片 a[0:2])赋值为等号右边的值 [4,5]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接