【问题标题】:itertools.product function creation producing unexpected resultsitertools.product 函数创建产生意想不到的结果
【发布时间】:2015-10-25 00:17:34
【问题描述】:

我在理解以下 sn-p 的结果时遇到了一点麻烦,我认为这是因为我对函数绑定感到困惑。为什么下面的 sn-ps 会产生不同的结果?

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-loop 中的最后一个变量,即使它为函数名称抓取了正确的变量。

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

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

【问题讨论】:

标签: python function scope closures function-binding


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    但是,由于您使用了生成器,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 的值,所有函数都会引用值。

    编辑:另请阅读@newacct 的答案

    【讨论】:

      猜你喜欢
      • 2021-10-04
      • 2021-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-20
      • 2011-06-04
      • 1970-01-01
      相关资源
      最近更新 更多