7

考虑以下函数,其输出应该是一系列迭代的笛卡尔积:

def cart(*iterables):
    out = ((e,) for e in iterables[0])
    for iterable in iterables[1:]:
        out = (e1 + (e2,) for e1 in out for e2 in iterable)
    return out

当生成器推导被列表推导替换时工作正常。当只有 2 个可迭代对象时也可以使用。但是当我尝试

print(list(cart([1, 2, 3], 'ab', [4, 5])))

我明白了

[(1, 4, 4), (1, 4, 5), (1, 5, 4), (1, 5, 5),
 (2, 4, 4), (2, 4, 5), (2, 5, 4), (2, 5, 5),
 (3, 4, 4), (3, 4, 5), (3, 5, 4), (3, 5, 5)]

为什么是这个而不是笛卡尔积?

4

1 回答 1

7

您正在创建生成器表达式,直到循环的下一次迭代才会迭代for iterable in iterables[1:]:。他们正在使用在运行时查找的闭包。

在这方面,生成器表达式本质上是小函数,它们创建自己的作用域,并且任何来自父作用域的名称都需要被视为闭包才能使其工作。迭代时会执行“函数”,然后才需要闭包并将其解析为所引用变量的当前值。

因此,您可以像这样创建一个生成器表达式:

(e1 + (e2,) for e1 in out for e2 in iterable)

whereiterable是从父范围(您的函数本地变量)获取的闭包。但是直到循环时的下一次迭代才完成查找,此时iterable是序列中的下一个元素

因此,对于您的输入[1, 2, 3], 'ab', [4, 5],您创建了一个生成器表达式,iterable = 'ab'但在您实际迭代时,for循环已经分配了一个新值并且现在是iterable = [4, 5]。当您最终迭代最终(链式)生成器时,只有最后一个分配iterable计数。

您正在有效地创建产品iterables[0], iterables[-1] * len(iterables) - 1iterables[1]通过 toiterables[-2]完全跳过,全部替换为iterables[-1].

您可以使用生成器函数来避免关闭问题,传入iterable以绑定到本地:

def gen_step(out, iterable):
    for e1 in out:
        for e2 in iterable:
            yield e1 + (e2,)

def cart(*iterables):
    out = ((e,) for e in iterables[0])
    for iterable in iterables[1:]:
        out = gen_step(out, iterable)
    return out

您可以对返回生成器表达式的 lambda 执行相同的操作:

def cart(*iterables):
    out = ((e,) for e in iterables[0])
    for iterable in iterables[1:]:
        out = (lambda it=iterable: (e1 + (e2,) for e1 in out for e2 in it))()
    return out
于 2017-07-13T10:30:59.837 回答