Python:遍历子列表

时间:2012-07-08 13:19:41

标签: python python-3.x generator slice

通常,当您想在Python中迭代列表的一部分时,最简单的方法就是对列表进行切片。

# Iterate over everything except the first item in a list
#
items = [1,2,3,4]
iterrange = (x for x in items[1:])

但切片运算符会创建一个新列表,在许多情况下甚至不需要这样做。理想情况下,我想要一种创建生成器的切片函数,而不是新的列表对象。通过创建一个使用range仅返回列表中某些部分的生成器表达式,可以实现与此类似的操作:

# Create a generator expression that returns everything except 
# the first item in the list
#
iterrange = (x for x, idx in zip(items, range(0, len(items))) if idx != 0)

但这有点麻烦。我想知道是否有更好,更优雅的方式来做到这一点。那么,切片列表的最简单方法是什么,以便创建生成器表达式而不是新的列表对象?

3 个答案:

答案 0 :(得分:20)

使用itertools.islice

import itertools

l = range(20)

for i in itertools.islice(l,10,15):
    print i

10
11
12
13
14

来自doc:

  

创建一个迭代器,从迭代中返回选定的元素

答案 1 :(得分:1)

在开始之前,要明确一点,在切片方法之间进行选择的正确顺序通常是:

  1. 使用常规切片(除了最长输入之外,复制所有内容的成本通常没有意义,并且代码要简单得多)。 如果输入可能不是可切片的序列类型,请将其转换为一个,然后进行切片,例如allbutone = list(someiterable)[1:]。与其他方法相比,这更简单,并且在大多数情况下通常更快。
  2. 如果常规切片不可行(不能保证输入是一个序列,并且在切片之前转换为序列可能会导致内存问题,或者输入很大,并且切片覆盖了大部分内容(例如,跳过10M元素list的前1000个元素和后1000个元素,因此可能是内存问题), itertools.islice通常是正确的解决方案,因为它很简单,而且性能成本通常并不重要。
  3. 当且仅当islice的性能缓慢到令人无法接受的程度时(制造每件商品都会增加一些开销,尽管可以承认这是一个很小的数目)。要跳过的数据量很小,而要包含的数据量很大(例如,OP跳过单个元素并保留其余元素的场景),请继续阅读

如果您遇到第3种情况,则说明您处于islice快速(相对)绕过初始元素的能力不足以弥补产生其余部分的增量成本的情况的元素。在这种情况下,您可以通过将问题从选择之后 n丢弃所有元素之前 n

对于这种方法,您可以将输入手动转换为迭代器,然后显式拉出并丢弃n值,然后迭代迭代器中剩余的内容(但不包含islice的每个元素的开销)。例如,对于输入myinput = list(range(1, 10000)),选择元素1到末尾的选项为:

# Approach 1, OP's approach, simple slice:
for x in myinput[1:]:

# Approach 2, Sebastian's approach, using itertools.islice:
for x in islice(myinput, 1, None):

# Approach 3 (my approach)
myiter = iter(myinput)  # Explicitly create iterator from input (looping does this already)
next(myiter, None) # Throw away one element, providing None default to avoid StopIteration error
for x in myiter:  # Iterate unwrapped iterator

如果要丢弃的元素数量较大,则最好借用the consume recipe from the itertools docs

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is None, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

这使方法普遍适用于跳过n元素到:

# Approach 1, OP's approach, simple slice:
for x in myinput[n:]:

# Approach 2, Sebastian's approach, using itertools.islice:
for x in islice(myinput, n, None):

# Approach 3 (my approach)
myiter = iter(myinput)  # Explicitly create iterator from input (looping does this already)
consume(myiter, n)      # Throw away n elements
# Or inlined consume as next(islice(myiter, n, n), None)
for x in myiter:        # Iterate unwrapped iterator

在性能方面,这对于大多数大型输入都是有意义的(例外:Python 3上的range本身已经针对普通切片进行了优化;普通切片无法在实际的range上胜过对象)。 ipython3个微基准测试(在CPython 3.6,64位Linux构建上)说明了这一点(设置中slurp的定义只是一种使开销最低的迭代方式可用的方法,因此我们可以最大程度地减少影响我们不感兴趣的东西):

>>> from itertools import islice
>>> from collections import deque
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... slurp(r[1:])
...
65.8 μs ± 109 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... slurp(islice(r, 1, None))
...
70.7 μs ± 104 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... ir = iter(r)
... next(islice(ir, 1, 1), None)  # Inlined consume for simplicity, but with islice wrapping to show generalized usage
... slurp(ir)
...
30.3 μs ± 64.1 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

显然,我的解决方案的额外复杂性通常不值得,但是对于中等大小的输入(在这种情况下为10K元素),性能优势是显而易见的。 islice的效果最差(少量),普通切片稍好一些(这强化了我的观点,即普通切片在有实际序列时几乎总是最佳解决方案),并且“转换为迭代器,相对而言,放弃“最初使用,使用休息”的方法获得了巨大的成功(远低于任何一种欠解决方案的时间的一半)。

只有很少的输入不会显示出这种优势,因为加载/调用iter / next尤其是islice的固定开销将超过节省的费用:

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... slurp(r[1:])
...
207 ns ± 1.86 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... slurp(islice(r, 1, None))
...
307 ns ± 1.71 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... ir = iter(r)
... next(islice(ir, 1, 1), None)  # Inlined consume for simplicity, but with islice wrapping to show generalized usage
... slurp(ir)
...
518 ns ± 4.5 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... ir = iter(r)
... next(ir, None)  # To show fixed overhead of islice, use next without it
... slurp(ir)
...
341 ns ± 0.947 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

但是正如您所看到的,即使对于10个元素,无islice的方法也不会差很多。每100个元素,无islice的方法比所有竞争对手都快,而200个元素,广义的next + islice击败了所有竞争对手(显然,它没有击败{{1 }}释放了islice的180 ns开销,但这是通过将islice元素概括为一个步骤来弥补的,而不是需要重复调​​用n来跳过更多元素来弥补多于一个元素)。普通next很少会在“跳过几行,保留很多”的情况下胜出,这是因为包装程序需要精确的逐元素开销(直到大约100K个元素,它才明显胜过微基准测试中切切的切入;它的内存效率高,但CPU效率低下),在“跳过很多,保留几下”的情况下,情况甚至会更糟(相对于急切的情况)。

答案 2 :(得分:0)

尝试itertools.islice:

http://docs.python.org/library/itertools.html#itertools.islice

iterrange = itertools.islice(items, 1, None)