有没有一种Pythonic的方法可以将循环变量闭合?

28

我刚看到了Eric Lippert的《不要在循环变量上使用闭包》,并进行了实验后发现同样的问题也存在于Python中,而且更难以解决。

>>> l = []
>>> for r in range(10):
...   def foo():
...      return r
...   l.append(foo)
...
>>> for f in l:
...   f()
...
9
9
9
# etc

此外,标准的C#解决方法并不起作用(我认为是因为Python中引用的本质不同)。

>>> l = []
>>> for r in range(10):
...   r2 = r
...   def foo():
...      return r2
...   l.append(foo)
...
>>> for f in l:
...   f()
...
9
9
9
# etc

我知道在Python中这不是太大的问题,因为它通常强调非闭包对象结构,但我想知道是否有明显的Pythonic方法来处理这个问题,或者我们是否必须采用JS方式的嵌套函数调用来创建实际上的新变量?

>>> l = []
>>> for r in range(10):
...     l.append((lambda x: lambda: x)(r))
...
>>> for f in l:
...     f()
...
0
1
2
# etc
1个回答

32

一种方法是使用具有默认值的参数:

l = []
for r in range(10):
    def foo(r = r):
        return r
    l.append(foo)

for f in l:
    print(f())
产生。
0
1
2
3
4
5
6
7
8
9

这段代码能够工作是因为它在 foo 的本地作用域中定义了一个r,并在定义 foo 时将默认值绑定到它上面。


另一种方法是使用函数工厂:

l = []
for r in range(10):
    def make_foo(r):
        def foo():
            return r
        return foo
    l.append(make_foo(r))

for f in l:
    print(f())
这段代码之所以能够运行,是因为它在make_foo的局部作用域中定义了r,并在调用make_foo(r)时绑定了一个值。稍后,在调用f()时,使用LEGB规则查找r。尽管rfoo的局部作用域中没有被找到,但在make_foo的封闭作用域中找到了r

1
哦,我喜欢那个默认参数的技巧。那个函数工厂在语义上类似于我的末尾双lambda事物。Lambda工厂却是可读性的大敌。 - quodlibetor
这两个都没有覆盖变量。对于更新没有用处。 - McKay
@McKay 没错,这个问题是关于如何不要闭合变量,而是在每次循环中保存不同的值。 - mhsmith
@mhsmith 是的,回顾这篇旧帖子,我今天同意你的看法。我完全不知道两年前自己在想什么。也许我当时试图编辑循环变量? - McKay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接