考虑以下函数,其输出应该是一系列迭代的笛卡尔积:
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)]
为什么这不是笛卡尔产品?
答案 0 :(得分:8)
您正在创建生成器表达式,这些表达式在for iterable in iterables[1:]:
循环的下一次迭代之前不会迭代。他们正在使用闭包,这些都是在运行时查找的。 p>
生成器表达式在这方面本质上是小函数,它们创建自己的作用域,并且父作用域中的任何名称都需要被视为闭包以使其工作。 '功能'迭代时执行,只需要关闭并解析为引用变量的当前值。
所以你创建一个这样的生成器表达式:
(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) - 1
到iterables[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