在Python中创建函数列表(Python函数闭包bug?)
我对函数式编程很了解。我想创建一个函数列表,每个函数选择列表中的不同元素。我把问题简化成了一个简单的例子。肯定这是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
语句是会被“执行”的。)