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