4

我注意到itertools文档中的食谱部分有一些有用的代码,比如consume,但我发现了一个问题。

from itertools import islice
numbers = iter(range(10))
for i in numbers:
    print i 
    next(islice(numbers, 3, 3), None)

上面的代码工作正常,它打印[0,4,8]next(islice(numbers, 3, 3), None)诀窍是从consume配方中获取的。

我们可以看到islice这里发挥着重要的作用。但是,当我用itertools.islicepython 文档中为该函数提供的代码替换时,代码没有按我预期的那样工作:

#from itertools import islice

def islice(iterable, *args):
    # islice('ABCDEFG', 2) --> A B
    # islice('ABCDEFG', 2, 4) --> C D
    # islice('ABCDEFG', 2, None) --> C D E F G
    # islice('ABCDEFG', 0, None, 2) --> A C E G
    s = slice(*args)
    it = iter(xrange(s.start or 0, s.stop or 200, s.step or 1))
    nexti = next(it)
    for i, element in enumerate(iterable):
        if i == nexti:
            yield element
            nexti = next(it)


numbers = iter(range(10))
for i in numbers:
    print i 
    next(islice(numbers, 3, 3), None)

输出与上面的输出不同;范围内的所有数字都被打印出来,并且islice()消耗线似乎没有做任何事情。

任何人都可以解释为什么会这样以及这里的工作方式islice吗?

4

1 回答 1

3

切片开始和停止参数相等,因此next(it)对 xrange 的调用会引发StopIteration

>>> s = slice(3, 3, None)
>>> it = iter(xrange(s.start or 0, s.stop or 200, s.step or 1))
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

并且slice()生成器函数立即终止。这只是合乎逻辑的,xrange()在这种情况下,该函数不会产生任何数字。

itertools文档是说明性的,并不意味着完整。您发现了一个极端情况,其中示例 Python 代码的工作方式与 C 实现的工作方式不同。

为了使 Python 代码也适用于这种情况,捕获StopIteration异常,并在循环中显式测试边缘情况:

def islice(iterable, *args):
    s = slice(*args)
    it = iter(xrange(s.start or 0, s.stop or 200, s.step or 1))
    try:
        nexti = next(it)
        consume = False
    except StopIteration:
        if s.start < 1:
            return
        nexti = s.start - 1
        consume = True
    for i, element in enumerate(iterable):
        if i == nexti:
            if consume:
                return
            yield element
            nexti = next(it)

Python 示例代码使用了一个slice()对象,使代码更紧凑,更易于阅读;C 代码执行自己的启动、停止和步进解析,并自行处理计数而不是委托给xrange(),因此不会遇到这种情况。

于 2013-07-28T08:24:59.620 回答