itertools.product 函数创建产生意外结果
itertools.product function creation producing unexpected results
我在理解以下代码片段的结果时遇到了一些困难,我认为这是因为我对函数绑定感到困惑。为什么以下代码片段会产生不同的结果?
import itertools
def make_funcs(lst):
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
yield f
## examples:
for f in make_funcs(range(2)):
print(f.func_name, f())
## prints:
>0 9
>1 8
## works as expected:
for f in make_funcs(range(2)):
for g in make_funcs(range(2)):
print(f.func_name, g.func_name, f() + g())
## prints:
>0 >0 18
>0 >1 17
>1 >0 17
>1 >1 16
另一方面:
## provides results that are counter-intuitive (to me, at least)
for f, g in itertools.product(make_funcs(range(2)), make_funcs(range(2))):
print(f.func_name, g.func_name, f() + g())
## prints:
>0 >0 16
>0 >1 16
>1 >0 16
>1 >1 16
在我看来,它只是 grabbing/using/binding
每个隐式 for
循环中用于计算的最后一个变量,即使它正在为函数获取正确的变量名字。
关于导致这些结果的范围界定、函数定义或闭包(或其他),我遗漏了什么?
注意:如果我在这个问题上放置的任何标签不相关,请随时删除它们 - 我把它们全部放置是因为我不确定问题出在哪里。
所有函数仍然引用变量val
。
def make_funcs(lst):
a = []
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
a.append(f)
return a
所有 打印结果为"counter intuitive"。
def make_funcs(lst):
a = []
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
a.append(f)
val = 10
return a
结果始终为 0。
然而,因为你使用了一个生成器,所以 val
的值只有在它被使用后才会改变,所以一切似乎都很好。当使用 itertools.product 时,docs 说它是这样做的:
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)
这意味着它首先迭代两个生成器(有效地改变 val
的值四次)然后才计算结果。
这一切都是因为 val
是在 make_funcs
的范围内定义的(而不是在 f
的范围内),所以如果对生成器的第二次调用更改了值在 val
中,所有函数都引用 new 值。
编辑:请同时阅读@newacct
的回答
另一个答案解释了为什么你会看到你所看到的——这是因为函数通过引用捕获外部变量,而同一个 val
变量被几个函数捕获,它们看到变量的变化。
补充一下,如果你想避免这种情况,如果你想按值捕获,一种方法是在内部函数中使用参数和默认参数:
def make_funcs(lst):
for val in lst:
def f(val=val):
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
yield f
我在理解以下代码片段的结果时遇到了一些困难,我认为这是因为我对函数绑定感到困惑。为什么以下代码片段会产生不同的结果?
import itertools
def make_funcs(lst):
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
yield f
## examples:
for f in make_funcs(range(2)):
print(f.func_name, f())
## prints:
>0 9
>1 8
## works as expected:
for f in make_funcs(range(2)):
for g in make_funcs(range(2)):
print(f.func_name, g.func_name, f() + g())
## prints:
>0 >0 18
>0 >1 17
>1 >0 17
>1 >1 16
另一方面:
## provides results that are counter-intuitive (to me, at least)
for f, g in itertools.product(make_funcs(range(2)), make_funcs(range(2))):
print(f.func_name, g.func_name, f() + g())
## prints:
>0 >0 16
>0 >1 16
>1 >0 16
>1 >1 16
在我看来,它只是 grabbing/using/binding
每个隐式 for
循环中用于计算的最后一个变量,即使它正在为函数获取正确的变量名字。
关于导致这些结果的范围界定、函数定义或闭包(或其他),我遗漏了什么?
注意:如果我在这个问题上放置的任何标签不相关,请随时删除它们 - 我把它们全部放置是因为我不确定问题出在哪里。
所有函数仍然引用变量val
。
def make_funcs(lst):
a = []
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
a.append(f)
return a
所有 打印结果为"counter intuitive"。
def make_funcs(lst):
a = []
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
a.append(f)
val = 10
return a
结果始终为 0。
然而,因为你使用了一个生成器,所以 val
的值只有在它被使用后才会改变,所以一切似乎都很好。当使用 itertools.product 时,docs 说它是这样做的:
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)
这意味着它首先迭代两个生成器(有效地改变 val
的值四次)然后才计算结果。
这一切都是因为 val
是在 make_funcs
的范围内定义的(而不是在 f
的范围内),所以如果对生成器的第二次调用更改了值在 val
中,所有函数都引用 new 值。
编辑:请同时阅读@newacct
的回答另一个答案解释了为什么你会看到你所看到的——这是因为函数通过引用捕获外部变量,而同一个 val
变量被几个函数捕获,它们看到变量的变化。
补充一下,如果你想避免这种情况,如果你想按值捕获,一种方法是在内部函数中使用参数和默认参数:
def make_funcs(lst):
for val in lst:
def f(val=val):
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
yield f