Python中的切片上的有效迭代

问题描述 投票:11回答:6

[在Python中通过切片操作进行迭代的效率如何?并且,如果不可避免地要有切片,那么有替代方法吗?

我知道列表上的切片操作为O(k),其中k是切片的大小。

x[5 : 5+k]  # O(k) copy operation

但是,当遍历列表的一部分时,我发现执行此操作的最简洁(也是最Python化的方法?)(无需求助于索引):

for elem in x[5 : 5+k]:
  print elem

但是我的直觉是,这仍然会导致子列表的复制,而不是简单地遍历现有列表。

python performance iteration slice
6个回答
5
投票

您可以使用itertools.islice从列表中获取切片式迭代器:

示例:

>>> from itertools import islice
>>> lis = range(20)
>>> for x in islice(lis, 10, None, 1):
...     print x
...     
10
11
12
13
14
15
16
17
18
19

更新:

[@ user2357112指出,islice的性能取决于切片的起点和可迭代的大小,几乎在所有情况下,正常切片都将很快,因此应首选。以下是一些时序比较:

对于大列表 islice在切片的开始点小于列表大小的一半时稍快或等于普通切片,对于较大的索引,普通切片是明显的赢家。

>>> def func(lis, n):
        it = iter(lis)
        for x in islice(it, n, None, 1):pass
...     
>>> def func1(lis, n):
        #it = iter(lis)
        for x in islice(lis, n, None, 1):pass
...     
>>> def func2(lis, n):
        for x in lis[n:]:pass
...     
>>> lis = range(10**6)

>>> n = 100
>>> %timeit func(lis, n)
10 loops, best of 3: 62.1 ms per loop
>>> %timeit func1(lis, n)
1 loops, best of 3: 60.8 ms per loop
>>> %timeit func2(lis, n)
1 loops, best of 3: 82.8 ms per loop

>>> n = 1000
>>> %timeit func(lis, n)
10 loops, best of 3: 64.4 ms per loop
>>> %timeit func1(lis, n)
1 loops, best of 3: 60.3 ms per loop
>>> %timeit func2(lis, n)
1 loops, best of 3: 85.8 ms per loop

>>> n = 10**4
>>> %timeit func(lis, n)
10 loops, best of 3: 61.4 ms per loop
>>> %timeit func1(lis, n)
10 loops, best of 3: 61 ms per loop
>>> %timeit func2(lis, n)
1 loops, best of 3: 80.8 ms per loop


>>> n = (10**6)/2
>>> %timeit func(lis, n)
10 loops, best of 3: 39.2 ms per loop
>>> %timeit func1(lis, n)
10 loops, best of 3: 39.6 ms per loop
>>> %timeit func2(lis, n)
10 loops, best of 3: 41.5 ms per loop

>>> n = (10**6)-1000
>>> %timeit func(lis, n)
100 loops, best of 3: 18.9 ms per loop
>>> %timeit func1(lis, n)
100 loops, best of 3: 18.8 ms per loop
>>> %timeit func2(lis, n)
10000 loops, best of 3: 50.9 us per loop    #clear winner for large index
>>> %timeit func1(lis, n)

对于小列表在几乎所有情况下,普通切片比islice快。

>>> lis = range(1000)
>>> n = 100
>>> %timeit func(lis, n)
10000 loops, best of 3: 60.7 us per loop
>>> %timeit func1(lis, n)
10000 loops, best of 3: 59.6 us per loop
>>> %timeit func2(lis, n)
10000 loops, best of 3: 59.9 us per loop

>>> n = 500
>>> %timeit func(lis, n)
10000 loops, best of 3: 38.4 us per loop
>>> %timeit func1(lis, n)
10000 loops, best of 3: 33.9 us per loop
>>> %timeit func2(lis, n)
10000 loops, best of 3: 26.6 us per loop

>>> n = 900
>>> %timeit func(lis, n)
10000 loops, best of 3: 20.1 us per loop
>>> %timeit func1(lis, n)
10000 loops, best of 3: 17.2 us per loop
>>> %timeit func2(lis, n)
10000 loops, best of 3: 11.3 us per loop

结论:

去普通切片。


8
投票

用途:

for elem in x[5 : 5+k]:

这是Pythonic!在您完成代码的[[profiled并确定这是一个瓶颈之前,请不要更改它-尽管我怀疑您是否会发现它是瓶颈的主要根源。


就速度而言,它可能是您最好的选择:

In [30]: x = range(100) In [31]: k = 90 In [32]: %timeit x[5:5+k] 1000000 loops, best of 3: 357 ns per loop In [35]: %timeit list(IT.islice(x, 5, 5+k)) 100000 loops, best of 3: 2.42 us per loop In [36]: %timeit [x[i] for i in xrange(5, 5+k)] 100000 loops, best of 3: 5.71 us per loop


就内存而言,这并不像您想象的那么糟糕。 x[5: 5+k]x的一部分的

shallow

副本。因此,即使x中的对象很大,x[5: 5+k]也会创建一个包含k个元素的新列表,这些元素引用的对象与x中的same对象相同。因此,您只需要额外的内存就可以创建一个列表,其中包含对现有对象的k个引用。那可能不会成为任何内存问题的根源。

4
投票
只需遍历所需的索引,就不需要为此创建新的切片:

for i in xrange(5, 5+k): print x[i]

授予:它看起来像是非Python的,但从不浪费额外内存的意义上讲,它比创建新的切片更有效。一种替代方法是使用迭代器,如@AshwiniChaudhary的答案所示。

2
投票
您已经在切片上执行了O(n)迭代。在大多数情况下,这比实际创建切片时要更加关注,后者完全发生在优化的C语言中。一旦遍历切片,则遍历切片的时间是制作切片的两倍,即使您什么都不用做:

>>> timeit.timeit('l[50:100]', 'import collections; l=range(150)') 0.46978958638010226 >>> timeit.timeit('for x in slice: pass', 'import collections; l=range(150); slice=l[50:100]') 1.2332711270150867

您可以尝试使用xrange遍历索引,但是考虑到检索列表元素所需的时间,它比切片要慢。即使您跳过该部分,它也不会超出切片效果:

>>> timeit.timeit('for i in xrange(50, 100): x = l[i]', 'l = range(150)') 4.3081963062022055 >>> timeit.timeit('for i in xrange(50, 100): pass', 'l = range(150)') 1.675838213385532

请勿为此使用itertools.islice!将从头开始遍历您的列表,而不是跳至__getitem__所需的值。以下是一些时序数据,显示了其性能如何取决于分片的起始位置:

>>> timeit.timeit('next(itertools.islice(l, 9, None))', 'import itertools; l = r ange(1000000)') 0.5628290558478852 >>> timeit.timeit('next(itertools.islice(l, 999, None))', 'import itertools; l = range(1000000)') 6.885294697594759
这里islice输给了常规切片:

>>> timeit.timeit('for i in itertools.islice(l, 900, None): pass', 'import itert ools; l = range(1000)') 8.979957560911316 >>> timeit.timeit('for i in l[900:]: pass', 'import itertools; l = range(1000)') 3.0318417204211983

这是在Python 2.7.5上,以防任何更高版本添加特定于列表的优化。

0
投票
[我认为,如果'k'太大(更好的方法是使用类似c的迭代(因为一个大的'k'-10000000000000-甚至可能使您等待大约10个小时才能在pythonic for循环中得到答案))

这是我想告诉你的事情:

i = 5 ## which is the initial value f = 5 + k ## which will be the final index while i < f: print(x[i]) i += 1

我假设这个函数可以在5个小时内完成操作(因为相当于python的loop可以运行大约10个小时),因为我告诉过那个大k,因为它只需一次从5变为10000000000005!每次使用'xrange()'的'range()'甚至切片本身(如上所述),都会使程序仅执行20000000000000次迭代,这可能导致更长的执行时间。 (据我了解,使用生成器方法将返回一个可迭代对象,该对象需要首先完全运行生成器,并且需要花两次时间才能完成工作;一个用于生成器本身,另一个用于“ for”循环))>

编辑:

在python 3中,生成器方法/对象不需要首先运行就可以为for循环创建可迭代对象

0
投票
接受的答案没有提供有效的解决方案。它按n的顺序排列,其中n是切片的起点。如果n很大,那将是一个问题。后续结论(

“ Go for normal slices]

© www.soinside.com 2019 - 2024. All rights reserved.