如何知道生成的序列最多一定长度

How to know a generated sequence is at most a certain length

我想知道生成的序列是否少于 2 个条目。

>>> def sequence():
...     for i in xrange(secret):
...         yield i

我低效的方法是创建一个列表,并测量它的长度:

>>> secret = 5
>>> len(list(sequence())) < 2
True

显然,这会消耗整个生成器。

在我的真实案例中,生成器可能正在穿越一个大型网络。我想在不消耗整个生成器或构建大列表的情况下进行检查。

有个recipe in the itertools documentation:

def take(n, iterable):
    "Return first n items of the iterable as a list"
    return list(islice(iterable, n))

这只构建一个最大长度的列表n,更好。

所以我可以说:

>>> len(take(2, sequence()) < 2

是否有更 pythonic、更有效的方法来做到这一点?

从 Python 3.4 开始,生成器可以实现 length hint. If a generator implements this it'll be exposed through the object.__length_hint__() method.

您可以使用 operator.length_hint() function 进行测试。

如果可用,您唯一的选择就是消耗元素,而使用take()配方是最有效的方法:

from operator import length_hint
from itertools import chain

elements = []
length = length_hint(gen, None)
if length is None:
    elements = list(take(2, gen))
    length = len(elements)
if length >= 2:
    # raise an error
# use elements, then gen
gen = chain(elements, gen)

使用take的解决方案使用islice,构建一个列表并取其长度:

>>> from itertools import islice
>>> len(list(islice(sequence(), 2))
2

为了避免创建列表,我们可以使用 sum:

>>> sum(1 for _ in islice(sequence(), 2)
2

这大约需要 70% 的时间:

>>> timeit('len(list(islice(xrange(1000), 2)))', 'from itertools import islice')
 1.089650974650752

>>> timeit('sum(1 for _ in islice(xrange(1000), 2))', 'from itertools import islice')
0.7579448552500647

总结一下:

>>> def at_most(n, elements):
...     return sum(1 for _ in islice(elements, n + 1)) <= n

>>> at_most(5, xrange(5))
True

>>> at_most(2, xrange(5))
False