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

5 投票
3 回答
659 浏览
提问于 2025-04-18 02:55

我对函数式编程很了解。我想创建一个函数列表,每个函数选择列表中的不同元素。我把问题简化成了一个简单的例子。肯定这是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(默认版本,2014年3月25日,11:07:05)

谢谢,
大卫

3 个回答

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 的值在调用时都是 4。也就是说,不管你调用多少次,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])
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])
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])

之所以这样有效,是因为当执行fun的定义时,_ndx的默认值会被计算并保存下来。(在Python中,def语句是会被“执行”的。)

撰写回答