为什么我的笛卡尔积函数不起作用?

时间:2017-07-13 10:20:59

标签: python cartesian-product generator-expression

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

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)]

为什么这不是笛卡尔产品?

1 个答案:

答案 0 :(得分:8)

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

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

所以你创建一个这样的生成器表达式:

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

其中iterable是从父作用域(您的函数本地)获取的闭包。但是在循环时,直到下一次迭代才进行查找,此时iterable是序列中的下一个元素

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

您实际上是在创建iterable以上的产品;完全跳过iterables[0], iterables[-1] * len(iterables) - 1iterables[1],全部由iterables[-2]替换。

您可以使用生成器函数来避免关闭问题,将iterables[-1]传入绑定到本地:

iterable

你可以用lambda返回生成器表达式:

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