在Python中创建函数列表(Python函数闭包bug?)

5

我很了解函数式编程。我想创建一个函数列表,每个函数可以选择列表中的不同元素。我已经将我的问题简化为一个简单的示例。肯定这是Python的一个bug:

fun_list = []
for i in range(5):
    def fun(e):
        return e[i]
    fun_list.append(fun)

mylist = range(10)
print([f(mylist) for f in fun_list])

"显然"它应该返回[0,1,2,3,4]。但它却返回[4, 4, 4, 4, 4]。我如何强制Python做正确的事情?(这之前没有注意到吗?还是我太蠢了?)
这是Python 3.4.0(默认,Mar 25 2014,11:07:05)
谢谢, David

3
当内部函数fun运行时,i的值为4。当您调用函数时,而不是定义函数时,行return e[i]会运行。 - Alex
3个回答

4

我该如何强制Python做正确的事情?

这里有一种方法:

fun_list = []
for i in range(5):
    def fun(e, _ndx=i):
        return e[_ndx]
    fun_list.append(fun)

mylist = range(10)
print([f(mylist) for f in fun_list])

这是因为当执行fundef语句时,默认值_ndx被计算并存储。(在Python中,def语句是执行的。)


2

这显然是Python的一个bug...

这是对作用域的误解。由于所有五个fun()实例都在同一作用域中定义,它们将共享该作用域中所有名称的相同值,包括i。为了解决这个问题,您需要将循环本身所在的作用域与使用的值分开。这可以通过在完全不同的作用域内定义函数来实现。

fun_list = []

def retfun(i):
  def fun(e):
    return e[i]
  return fun

for i in range(5):
  fun_list.append(retfun(i))

mylist = range(10)
print([f(mylist) for f in fun_list])

1
这不是作用域的误解。我期望函数在当前环境中被评估,而不是在未来的某个环境中。合理的方法是将函数与当前环境的副本相关联,而不是在所有值都改变后的环境中。这就是为什么我们在特定的上下文中定义函数的原因... - user3539674
如果给它一个当前作用域的副本,那么它将无法与非本地作用域一起工作,因为这些作用域中的值永远不会改变。 - Ignacio Vazquez-Abrams

1
你可以使用 itemgetter 来完成这个任务:
from operator import itemgetter
fun_list = []
for i in range(5):
    fun_list.append(itemgetter(i))

mylist = range(10)
print([f(mylist) for f in fun_list])

在您的情况下,您将一个函数分配给所有引用全局变量i的元素,并使用调用时的i值(即所有调用的i值为4)。您需要一些类型的柯里化
同样的代码,不包含itemgetter:
def indexer(i): return lambda y: y[i]

fun_list = []
for i in range(5):
    fun_list.append(indexer(i))

mylist = range(10)
print([f(mylist) for f in fun_list])

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