7 python generator-expression cartesian-product
考虑以下函数,其输出应该是一系列迭代的笛卡尔积:
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
Run Code Online (Sandbox Code Playgroud)
当生成器理解被列表推导替换时,工作正常.当只有2个迭代时也可以工作.但是,当我尝试
print(list(cart([1, 2, 3], 'ab', [4, 5])))
Run Code Online (Sandbox Code Playgroud)
我明白了
[(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)]
Run Code Online (Sandbox Code Playgroud)
为什么这个而不是笛卡儿产品?
您正在创建生成器表达式,这些表达式在for iterable in iterables[1:]:循环的下一次迭代之前不会迭代.他们正在使用闭包,这些闭包在运行时被查找.
在这方面,Generator表达式本质上是小函数,它们创建自己的作用域,并且父作用域中的任何名称都需要被视为闭包以使其工作.迭代时执行'function',只需要闭包并解析为引用变量的当前值.
所以你创建一个这样的生成器表达式:
(e1 + (e2,) for e1 in out for e2 in iterable)
Run Code Online (Sandbox Code Playgroud)
iterable从父作用域(您的函数本地)获取的闭包在哪里.但是在循环时,直到下一次迭代才会进行查找,此时iterable该序列是序列中的下一个元素.
因此,对于您的输入[1, 2, 3], 'ab', [4, 5],您创建一个生成器表达式,iterable = 'ab'但是当您实际迭代时,for循环已分配一个新值,现在是iterable = [4, 5].当你最终迭代最终(链式)生成器时,只有最后一个赋值iterable计数.
您正在有效地创建产品iterables[0], iterables[-1] * len(iterables) - 1; iterables[1]通过iterables[-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
Run Code Online (Sandbox Code Playgroud)
您可以使用返回生成器表达式的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
Run Code Online (Sandbox Code Playgroud)