结合无限与有限迭代器

combine infinite with finite iterator

我有两个嵌套循环,外部循环使用无限生成器,内部循环使用有限迭代器(数字列表):

for j in itertools.count():
   for q in Q:
       ...

在给定条件下,我需要从两个循环中跳出 break,这使得代码不够优雅(我必须设置一个标志来触发第二个 break)。为了避免这种情况,我想我会使用

将两个循环合二为一
for j, q in itertools.product(itertools.count(), Q):
    ...

但是当我这样做时,我的计算机变得越来越慢,显然是在交换内存,直到我不得不终止 Python 进程。

product 的文档说

the actual implementation does not build up intermediate results in memory

所以我假设 product 的结果是一个生成器,它根据需要创建产品的元素。但是减速和交换与此相反。

我做错了什么?我怎样才能实现我想要的,将无限生成器与列表组合在一起的单个循环?

您可以使用推导式创建生成器。 Comprehensions 支持使用多个 fors

一次迭代多个可迭代对象
for j, q in ((j, q) for j in itertools.count() for q in Q):
    ...

您的文档引用来自 the following context:

This function is roughly equivalent to the following code, except that the actual implementation does not build up intermediate results in memory:

def product(*args, repeat=1):
    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
    # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
    pools = [tuple(pool) for pool in args] * repeat
    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]
    for prod in result:
        yield tuple(prod)

中间结果都是result个列表。实际的 itertools.product 实现不需要构建这些。但是,它确实构建了类似于 pools.

的东西

itertools.product 需要能够重复迭代其输入,因此它将所有输入具体化为元组。当 repeat1 时,此要求不适用于第一个输入,但实施无论如何都会具体化所有输入,可能是为了保持一致性或易于实施。 itertools.count() 无法完全实现,因此您看到的结果。

您有一个选择是生成器表达式。以下循环:

for j, q in ((j, q) for j in itertools.count() for q in Q):

会像您希望 itertools.product 那样表现,尽管它会比普通嵌套循环慢(因为生成器的开销)。性能损失只是一个常数因素,它不会减慢循环体的速度,所以很少有问题。

如果你可以将嵌套循环分解成一个函数,你也可以使用 return 而不是 break:

def helper_function(stuff):
    for j in itertools.count():
        for q in Q:
            ...
            if whatever:
                return
            ...

添加以防对任何人都有用,并且基于,可以使用异常来中断或继续外循环,例如:

class ContinueOuter(Exception): pass
class BreakOuter(Exception): pass

try:
    for i in range(10):  # loop we want to break or continue from
        try:
            for j in range(3):
                print(i, j)
                if (i, j) == (3, 1):
                    raise ContinueOuter
                elif (i, j) == (4, 0):
                    raise BreakOuter
        except ContinueOuter:
            pass
except BreakOuter:
    pass