在Python中切片列表而不生成副本

115 投票
5 回答
80022 浏览
提问于 2025-04-16 12:36

我遇到了一个问题。

给定一个整数列表 L,我需要生成所有的子列表 L[k:],其中 k 的范围是从 0 到 len(L) - 1而且不能生成副本

我该如何在 Python 中做到这一点?是不是可以用某种缓冲对象?

5 个回答

9

这是一个简单的替代方案,用来代替islice,它的好处是不会遍历那些不需要的列表项:

def listslice(xs, *args):
    for i in range(len(xs))[slice(*args)]:
        yield xs[i]

使用方法:

>>> xs = [0, 2, 4, 6, 8, 10]

>>> for x in listslice(xs, 2, 4):
...     print(x)
4
6
31

根据你要做的事情,你可能可以使用 islice

因为它是通过迭代来工作的,所以不会生成新的列表,而是会创建一些迭代器,这些迭代器会根据你请求的范围,从原始列表中“产出”元素。

187

简短回答

切片列表并不会生成列表中对象的副本;它只是复制了这些对象的引用。这就是问题的答案。

详细回答

测试可变和不可变值

首先,我们来验证这个基本说法。即使是像整数这样的不可变对象,复制的也只是引用。这里有三个不同的整数对象,它们的值都是一样的:

>>> a = [1000 + 1, 1000 + 1, 1000 + 1]

它们的值相同,但你可以看到它们是三个不同的对象,因为它们的 id 不同:

>>> map(id, a)
[140502922988976, 140502922988952, 140502922988928]

当你对它们进行切片时,引用保持不变。没有创建新的对象:

>>> b = a[1:3]
>>> map(id, b)
[140502922988952, 140502922988928]

使用不同的对象但值相同,说明复制过程并不关心 字符串的内部化 — 它只是直接复制引用。

用可变值进行测试也得出相同的结果:

>>> a = [{0: 'zero', 1: 'one'}, ['foo', 'bar']]
>>> map(id, a)
[4380777000, 4380712040]
>>> map(id, a[1:]
... )
[4380712040]

检查剩余的内存开销

当然,引用本身是被复制的。在64位机器上,每个引用占用8个字节。而每个列表本身也有72个字节的内存开销:

>>> for i in range(len(a)):
...     x = a[:i]
...     print('len: {}'.format(len(x)))
...     print('size: {}'.format(sys.getsizeof(x)))
... 
len: 0
size: 72
len: 1
size: 80
len: 2
size: 88

正如 Joe Pinsonault 提醒我们的,这些开销是会累积的。而整数对象本身并不大——它们的大小是引用的三倍。所以从绝对意义上来说,这样可以节省一些内存,但从渐进的角度看,能够有多个列表作为同一内存的“视图”可能会更好。

通过使用视图来节省内存

不幸的是,Python没有简单的方法来生成“视图”对象来查看列表。或者我应该说“幸运”!这意味着你不必担心切片来自哪里;对原始列表的更改不会影响切片。总体来说,这让你理解程序的行为变得更简单。

如果你真的想通过使用视图来节省内存,可以考虑使用 numpy 数组。当你对 numpy 数组进行切片时,切片和原始数组之间是共享内存的:

>>> a = numpy.arange(3)
>>> a
array([0, 1, 2])
>>> b = a[1:3]
>>> b
array([1, 2])

当我们修改 a 并再次查看 b 时会发生什么呢?

>>> a[2] = 1001
>>> b
array([   1, 1001])

但这意味着你必须确保在修改一个对象时,不会无意中修改另一个对象。这就是使用 numpy 的权衡:计算机的工作量减少了,但程序员的工作量增加了!

撰写回答