函数式编程与列表推导等价实现:Mark Lutz示例写法探讨
先看Mark Lutz在《Learning Python》里的这个列表推导示例:
>>> [(x,y) for x in range(5) if x%2==0 for y in range(5) if y%2==1] [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
我尝试用嵌套的map和filter写出等价的函数式实现,得到的代码是:
>>> list(map(lambda x:list(map(lambda y:(y,x),filter(lambda x:x%2==0,range(5)))), filter(lambda x:x%2==1,range(5)))) [[(0, 1), (2, 1), (4, 1)], [(0, 3), (2, 3), (4, 3)]]
(注:原输出里的...是截断了,实际是嵌套列表)
现在想请教:这个实现是不是原列表推导的正确等价写法?有没有更优的函数式实现方式?
你的实现并不等价,问题出在结构上
先直接说结论:你写的代码不是等价实现。原列表推导的结果是一个扁平的元组列表,但你的代码输出的是嵌套列表——外层是每个奇数y对应的列表,内层是(y,x)的元组(而且元组的顺序还反了,原推导是(x,y),你这里写成了(y,x))。
要对齐原列表推导的逻辑,得先理清楚原推导的执行顺序:
- 遍历
range(5)里的x,筛选出偶数x - 对每个符合条件的
x,遍历range(5)里的y,筛选出奇数y - 把每对
(x,y)收集成扁平列表
你的代码刚好把x和y的遍历顺序反过来了,还套了两层列表,所以结构完全不对。
正确的等价函数式写法
要写出和原列表推导完全等价的map+filter实现,需要把嵌套的遍历逻辑转成扁平的迭代,这里可以用itertools.chain来扁平化,或者调整map的嵌套方式:
写法1:用itertools.chain扁平化嵌套map的结果
from itertools import chain list(chain.from_iterable( map(lambda x: map(lambda y: (x, y), filter(lambda y: y % 2 == 1, range(5))), filter(lambda x: x % 2 == 0, range(5))) ))
这个代码的逻辑是:
- 先筛选出偶数
x,对每个x,生成对应的奇数y的(x,y)元组迭代器 - 再用
chain.from_iterable把这些迭代器合并成一个扁平的迭代器,最后转成列表,结果和原推导完全一致。
写法2:用嵌套生成器替代部分map(更易读)
如果允许混合生成器表达式(毕竟函数式编程也不排斥生成器),可以写得更简洁:
list((x, y) for x in filter(lambda x: x%2==0, range(5)) for y in filter(lambda y: y%2==1, range(5)))
这其实是把列表推导里的条件换成了filter,逻辑完全对齐原推导,可读性也更好。
更优的函数式实现
如果追求更简洁、更符合Python函数式风格的写法,推荐用itertools.product来生成笛卡尔积,再用filter筛选符合条件的元组:
from itertools import product list(filter(lambda t: t[0]%2==0 and t[1]%2==1, product(range(5), range(5))))
product(range(5), range(5))会生成所有(x,y)的笛卡尔积,然后filter只保留x是偶数、y是奇数的元组,结果和原推导完全一致,而且代码更简洁直观,性能也不错(因为product是底层优化的迭代器)。
内容的提问来源于stack exchange,提问作者Vladimir Zolotykh

