itertools.product函数创建产生意外结果

时间:2015-08-03 00:42:02

标签: python function scope closures function-binding

我在理解下面代码片段的结果时遇到了一些麻烦,并认为这是因为我对功能绑定感到困惑。为什么以下片段会产生不同的结果?

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

在我看来,它只是抓取/使用/ binding每个隐式for循环中的最后一个变量进行计算,即使它正在为函数名称获取正确的变量。

关于范围或函数定义或闭包(或其他)导致这些结果的我缺少什么?

注意:如果我在这个问题上提出的任何标签都无关紧要,请随意删除它们 - 我把它们全部放在一起因为我不确定是什么问题。

2 个答案:

答案 0 :(得分:2)

另一个答案解释了为什么你看到你所看到的 - 它是因为函数通过引用捕获外部变量,并且几个函数捕获了相同的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

答案 1 :(得分:1)

所有函数仍引用变量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

所有的结果打印出"反直觉"。

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&#39。

但是,因为你使用了一个生成器,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的值改变四次),然后才计算结果。

这一切都发生是因为valmake_funcs的范围内定义(而不在f的范围内),所以如果第二次调用生成器会更改{的值{1}},所有函数都引用 new 值。

修改:请同时阅读@newacct

的答案