列表推导式中使用生成器表达式未达预期效果的问题
为什么嵌套生成器表达式和嵌套列表推导式结果不一样?
先看你给出的两种场景:
场景1:嵌套列表推导式(结果符合预期)
# 将列表推导式作为列表推导式的第一个表达式 >>> l = [[i*2+x for x in j] for i,j in zip([0,1],[range(4),range(4)])] >>> l[0] [0, 1, 2, 3] >>> l[1] [2, 3, 4, 5]
这里的核心是列表推导式是立即求值的:外层循环每跑一次,内层的列表推导式就会立刻把当前的i和j值代入,计算出所有元素并生成列表,最终把这些实实在在的列表保存到l里。所以l[0]用的是循环第一次的i=0,l[1]用的是第二次的i=1,结果完全符合预期。
场景2:嵌套生成器表达式(结果不符合预期)
# 将生成器表达式作为第一个表达式 >>> l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])] >>> list(l[0]) # 实际结果会是[2,3,4,5],而非预期的[0,1,2,3] [2, 3, 4, 5] >>> list(l[1]) [2, 3, 4, 5]
问题出在生成器的延迟求值特性上:生成器表达式在创建的时候根本不会计算结果,它只是保存了对变量i和j的引用。等你调用list(l[0])去迭代生成器时,外层的列表推导式早就跑完了——这时候i已经变成了循环的最后一个值1,j也变成了最后一个range(4)。所以两个生成器在真正干活的时候,用的都是同一个i=1,自然输出一样的结果。
怎么解决这个问题?
要让每个生成器绑定循环时i和j的当前值,而不是盯着外层的变量不放,可以用局部变量固定值的方式,比如借助lambda的默认参数来绑定:
l = [(lambda i_val=i, j_val=j: (i_val*2 + x for x in j_val))() for i,j in zip([0,1],[range(4),range(4)])] >>> list(l[0]) [0, 1, 2, 3] >>> list(l[1]) [2, 3, 4, 5]
这里lambda的默认参数i_val=i会在每次循环时,把当前的i值牢牢绑定到局部的i_val上,生成器引用的是这个固定的局部变量,再也不会受外层循环结束后i值变化的影响。
如果觉得lambda写法有点绕,也可以用更直观的普通循环:
l = [] for i,j in zip([0,1],[range(4),range(4)]): current_i = i current_j = j gen = (current_i*2 + x for x in current_j) l.append(gen) >>> list(l[0]) [0,1,2,3] >>> list(l[1]) [2,3,4,5]
这种写法虽然不是一行式的列表推导,但逻辑更清晰,同样能避开变量引用的坑。
内容的提问来源于stack exchange,提问作者Mike Lui
相关产品推荐
相关产品推荐

