itertools.product是否惰性地评估其参数?

9
以下代码在 Python 3.6 中不会输出任何内容。
from itertools import product, count

for f in product(count(), [1,2]): 
    print(f)

相反,它只是停在那里并烧掉CPU。问题似乎是如果product超过一个无限空间,它不会返回迭代器,因为它首先评估完整的product。这令人惊讶,因为product应该是一个生成器。
我本来期望它开始计数(到无限),就像这个生成器的行为一样(直接从文档中获取):
for tup in ((x,y) for x in count() for y in [1,2]):
    print(tup)

但是,尽管我的生成器立即开始计数,使用 product 的生成器根本不会计数。

itertools 中的其他工具表现符合我的预期。例如,以下代码:

for f in takewhile(lambda x: True, count()): 
    print(f)

会打印一系列数字,因为takewhile是惰性的。


2
DeepSpace的回答是误导性的:就我所知,product不是懒惰的,因为它需要(我假设)无限的时间才能返回,而文档中等效的示例则按照我的预期执行。 - Shep
3个回答

10

itertools.product 生成其结果是惰性的,但对于参数不是这样。它们会被急切地求值。每个可迭代的参数首先被转换为元组:

参数的求值(不是结果的生成)与文档中显示的Python实现非常相似:

...
pools = [tuple(pool) for pool in args] * repeat

然而,在CPython实现中,pools是一个元组嵌套的元组:

for (i=0; i < nargs ; ++i) {
     PyObject *item = PyTuple_GET_ITEM(args, i);
     PyObject *pool = PySequence_Tuple(item);   /* here */
     if (pool == NULL)
         goto error;
     PyTuple_SET_ITEM(pools, i, pool);
     indices[i] = 0;
 }

这是因为product有时需要多次遍历可迭代对象,如果参数仅保留作为只能被消耗一次的迭代器,则不可能达到此目的。

itertools.count对象中实际上无法构建元组。在传递给product之前,请考虑使用itertools.islice将其切片到合理长度。


1
这个答案让我避免了向Python提交错误报告,干得好。 - Shep

1
我发现了
for tup in ((x,y) for x in count() for y in [1,2]):
    print(tup)

做我所期望的事情。这很奇怪,因为在文档中被列为等效物。这似乎是itertools.product中的一个错误,但考虑到它是多么标准化,这似乎不太可能。


1
它被列为“大致”等同,并且声明返回相同的值。我认为这确实给了一个可能性,即其中一个是懒惰的,而另一个则不是。 - Michael Mior

0

问题似乎在于product从不返回迭代器

不,product已经是“惰性”的了。

问题在于count()计数到无限。根据count文档

等同于:

def count(start=0, step=1):
    # count(10) --> 10 11 12 13 14 ...
    # count(2.5, 0.5) -> 2.5 3.0 3.5 ...
    n = start
    while True:
        yield n
        n += step

你的代码基本上相当于执行以下操作:

def count():
    i = 0
    while True:
        yield i
        i += 1

count()

我添加了一个示例,其中包含我从惰性求值函数中得到的预期结果。 - Shep
1
@Shep count()首先被评估,其余的代码从某种意义上说都不会被执行。 - Chris_Rands
@Shep,你对所需的输出仍不清楚,你是指像这样吗?for c in count(): print(list(product([c],[1,2]))) - Chris_Rands
我相信是这样的,是的。我还添加了一个生成器,它正好做我需要的事情。问题是是否有内置函数可以执行product正在执行的操作,但是可以惰性地评估count() - Shep
2
好的,直接从文档中获取:"product(A, B) 的返回结果与 ((x,y) for x in A for y in B) 相同。" 当 Acount() 时,我认为这不正确。 - Shep
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接