【问题标题】:Slicing a list in Python without generating a copy在 Python 中切片列表而不生成副本
【发布时间】:2011-07-05 03:07:32
【问题描述】:

我有以下问题。

给定一个整数列表L,我需要生成所有子列表L[k:]for k in [0, len(L) - 1],不生成副本。

如何在 Python 中实现这一点?以某种方式使用缓冲区对象?

【问题讨论】:

  • 您不想复制列表本身,还是不想复制其中的对象?
  • 我不想在生成切片时复制其中的对象。
  • Python 默认不进行复制。
  • 你怎么知道有复制品?您是否注意到资源有所增加?

标签: python list slice


【解决方案1】:

根据您的操作,您可能可以使用islice。

由于它通过迭代进行操作,因此它不会创建新列表,而是会根据其范围的请求从原始列表中创建 yield 元素的迭代器。

【讨论】:

  • 这里的坏处是 islice 没有利用实现 getitem 方法的对象,将所有内容都视为迭代器,因此它总是从第一个元素开始迭代直到它到达列表的第一个位置以开始产生范围内的值。
【解决方案2】:

简短的回答

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

长答案

可变和不可变值的测试

首先,让我们测试一下基本声明。我们可以证明,即使在整数等不可变对象的情况下,也只会复制引用。下面是三个不同的整数对象,每个对象都有相同的值:

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

它们具有相同的值,但是您可以看到它们是三个不同的对象,因为它们具有不同的ids:

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

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

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

使用具有相同值的不同对象表明复制过程不会打扰interning——它只是直接复制引用。

使用可变值进行测试会得到相同的结果:

>>> 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 reminds us,开销加起来。整数对象本身并不是很大——它们比引用大三倍。因此,这在绝对意义上为您节省了一些内存,但渐近地说,能够在同一内存中拥有多个“视图”列表可能会很好。

使用视图节省内存

不幸的是,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 时的权衡:计算机的工作量减少,程序员的工作量增加!

【讨论】:

  • 在不可变对象(例如元组)中,引用是不可变的,但它们引用的项可以是可变的。所以 3 个列表的元组不能改变,它总是引用相同的 3 个列表,但每个列表的内容可以像任何列表一样改变。
  • 虽然答案是正确的,但这个例子并没有真正证明它,因为小整数被困住了;尝试做id(2) 甚至id(1+1)。一个更好的例子是使用a = [[], [], []]。
  • 或者实际上,在进一步阅读之后,问题实际上指定列表是由整数组成的,所以我觉得很奇怪作者甚至担心项目复制到首先! (我宁愿认为 OP 没有完全理解您的澄清要求,实际上是想在原始列表中获得“观点”)
  • 这个答案是正确的,但我认为值得指出的是,如果你有非常大的数组,复制指针数组仍然很昂贵
  • @ExpHP,这可能是真的。我尽量不要啰嗦,但我想像这样的问题是不可能的!已编辑。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    我写了一个ListView 类,它甚至可以避免复制列表的脊椎:

    https://gist.github.com/3noch/b5f3175cfe39aea71ca4d07469570047

    这支持嵌套切片,因此您可以继续切片到视图中以获得更窄的视图。例如:ListView(list(range(10)))[4:][2:][1] == 7。

    请注意,这还没有完全成熟,当底层列表与测试套件一起发生变异时,需要进行更多的错误检查。

    【讨论】:

      【解决方案5】:

      一般来说,列表切片是最好的选择。

      这是一个快速的性能比较:

      from timeit import timeit
      from itertools import islice
      
      for size in (10**4, 10**5, 10**6):
          L = list(range(size))
          S = size // 2
          def sum_slice(): return sum(L[S:])
          def sum_islice(): return sum(islice(L, S, None))
          def sum_for(): return sum(L[i] for i in range(S, len(L)))
      
          assert sum_slice() == sum_islice()
          assert sum_slice() == sum_for()
      
          for method in (sum_slice, sum_islice, sum_for):
              print(f'Size={size}, method={method.__name__}, time={timeit(method, number=1000)} ms')
      

      结果:

      Size=10000,   method=sum_slice,  time=0.0298 ms
      Size=10000,   method=sum_islice, time=0.0449 ms
      Size=10000,   method=sum_for,    time=0.2500 ms
      Size=100000,  method=sum_slice,  time=0.3262 ms
      Size=100000,  method=sum_islice, time=0.4492 ms
      Size=100000,  method=sum_for,    time=2.4849 ms
      Size=1000000, method=sum_slice,  time=5.4092 ms
      Size=1000000, method=sum_islice, time=5.1139 ms
      Size=1000000, method=sum_for,    time=26.198 ms
      

      【讨论】:

        猜你喜欢
        • 2022-01-25
        • 2012-08-06
        • 1970-01-01
        • 2011-10-17
        • 1970-01-01
        • 2017-07-27
        • 1970-01-01
        相关资源
        最近更新 更多