Python 下避免 filter, map (reduce) 重复遍历 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
mymusise
V2EX    Python

Python 下避免 filter, map (reduce) 重复遍历

  •  
  •   mymusise
    mymusise 2017 年 2 月 9 日 6152 次点击
    这是一个创建于 3284 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Hi 第一次来写东西,大家多多支持 (入题) 最近某天上班路上在微薄看到一哥们写的《在 Javascript 中实现 LINQ 》看到里面关于 C#的 Linq 在实现 filter 和 map 的时候说道(reduce 已经在 python3 从全局空间去掉了,所以标题里面我加了个括号),如果同时调用类似 filter 和 map 这样的操作去遍历 List 的时候,实际上只遍历了一遍,像下面这样:

    var array = new []{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var sum = array.Where(n => n % 2 === 0) .Select(n => n + 3) .Aggregate((sum, n) => sum + n, 0); 

    然后文章后面提到 Javascript 中直接调用 filter 和 map 的时候,会重复遍历 Array ,比如像下面代码这样:

    let array = [1, 2, 3, 4] let sum = array.filter(n => n % 2 === 0) .map(n => n + 3) .reduce((sum, n) => sum + n, 0) 

    这样的话会先把 arrayfilter 成[2,4],然后再 map 成[5, 7],然后再 reduce 成 12 ,所以这个过程 array 会被重复遍历。

    好了,下面说下 Python ,看完文章的时候然后我就好奇 Python 里面的 filter 和 map 是不是也是这样,会重复去遍历 List ,于是做了个实验: 像平时我们喜欢的函数式的写法:

    In [1]: numbers = [1,2,3,4,5,6] In [2]: list(map(lambda x: x + 1, filter(lambda x: x % 2 == 0, numbers))) Out[2]: [3, 5, 7] 

    为了看清楚是不是重复遍历了 numbers 这个 List ,把 lambda 改写成个普通的 function 打印出来看看

    In [3]: def is_even(number): ...: print('filter') ...: return True if number % 2 == 0 else False In [4]: def inc(number): ...: print('map') ...: return number + 1 In [5]: list(map(inc, filter(is_even, numbers))) filter filter filter filter filter filter map map map Out[6]: [3, 5, 7] 

    上面可以看到, Python 这样直接调用 filter 和 map 也是会重复遍历 list 的。不过那哥们的文中提到后来能在 Javascript 实现 Linq ,主要因为 ES6 支持 yield 和 Generator Function ,所以我想 Python 这两个都支持肯定也是可以实现类似 Linq 这样不重复遍历的 Magic 。

    然后,再试了下之前很喜欢的一个函数式库 pyfunctional。这是个很值来安利一波的一个库,用了这个库后,摆脱了原生那种很丑的写法

    # before list(map(inc, filter(is_even, numbers))) # afater seq(numbers)\ .filter(is_even)\ .map(inc)\ .to_list() 

    嗯,很 Js 的写法....

    好,回到正题,如果像上面这样调用 functional 时,发现整个过程只遍历的一次 List

    In [7]: from functional import seq In [8]: seq(numbers)\ ...: .filter(is_even)\ ...: .map(inc)\ ...: .to_list() filter filter map filter filter map filter filter map Out[8]: [3, 5, 7] 

    果然是个好货,安利一波

    20 条回复    2017-02-10 06:47:11 +08:00
    skydiver
        1
    skydiver  
       2017 年 2 月 9 日 via Android
    有什么区别吗…都是六次 filter 三次 map …只是顺序不一样
    czheo
        2
    czheo  
       2017 年 2 月 9 日
    按照你这个说法,我是不是也可以安利一下我的
    https://github.com/czheo/syntax_sugar_python

    ryd994
        3
    ryd994  
       2017 年 2 月 9 日   1
    不存在重复遍历, map 的结果是个新 list
    一定要说区别的话在于直接 map 有额外拷贝,是新 list ,内存占用大
    直接计算出最终结果的 list ,如果不需要全部结果的话,这样就浪费了

    用 itertools.map 就没这个问题
    python 里面 iterator 就是用 yield
    ryd994
        4
    ryd994  
       2017 年 2 月 9 日
    顺带一提,如果是明确需要所有结果,而且不缺内存的话,一般写法(非 iterator )会快一点。上下文切换也是有成本的,哪有一个函数常驻指令缓存快。
    wjidea
        5
    wjidea  
       2017 年 2 月 9 日
    @ryd994 itertools +1
    ryd994
        6
    ryd994  
       2017 年 2 月 9 日
    再说明白点:
    numbers = [1,2,3,4,5,6]
    n1 = filter(lambda x: x % 2 == 0, numbers)
    n2 = list(map(lambda x: x + 1, n1))

    三次遍历分别在 numbers, n1, n2 上,不存在重复遍历一个 list 的问题
    kindjeff
        7
    kindjeff  
       2017 年 2 月 9 日 via iPhone   1
    Python3 的 map 和 filter 返回的就是迭代器呀。如果你用 Python3 ,结果就是最后输出的那样。
    ChefIsAwesome
        8
    ChefIsAwesome  
       2017 年 2 月 9 日 via Android
    这种优化就是把普通 list 变成 lazy 的 list ,然后内部保存了一个 command list 。每次往那个 chain 后头加个 filter , map 之类的方法的时候,往内部的 command list 里加东西。最后对这个 lazy list 取值的时候再去对 list 里的每个 item 执行那个 command list 。本质是个 monad ( linq 就是 monad )。
    从遍历的角度看,虽然 list 只遍历一次。但是那个 command list 每次都要遍历。速度未必比每次遍历都快。
    ik
        9
    ik  
       2017 年 2 月 9 日 via iPhone
    翻到最后居然没有二维码,害的再翻回去看一遍
    quxw
        10
    quxw  
       2017 年 2 月 9 日
    silymore
        11
    silymore  
       2017 年 2 月 9 日 via Android
    @ik 我也是进来看二维码的,失策了
    mymusise
        12
    mymusise  
    OP
       2017 年 2 月 9 日
    @skydiver 嗯,打印出来的次数是一样,但还是有点不一样。按照之前的理解,如果直接去 filter/map 的话,从输出结果看是先扫了一遍 list , filter 出新的 list ,然后再拿得到新 list 去 map ,不过楼下有哥们说 python3 返回的就是迭代器,试了下好像就没有这个问题了
    mymusise
        13
    mymusise  
    OP
       2017 年 2 月 9 日
    @czheo 好东西,学习了,不过刚试了下有个 bug ,提了个 request 。
    miao1007
        14
    miao1007  
       2017 年 2 月 9 日 via Android
    这个不就是惰性求值吗,类似 Guava
    mymusise
        15
    mymusise  
    OP
       2017 年 2 月 9 日
    @ryd994 嗯,说的对,对比了下 pyfunctional 和直接 filter/map 和非 iterator 写法,耗时的确是 pyfunctional>自带的 filter > 非 iterator 。 @kindjeff 换成 Python3 的确没有这个问题了。
    WangYanjie
        16
    WangYanjie  
       2017 年 2 月 9 日
    按我的理解, map, filter, reduce 都是不太推荐的,更好的做法应该是 list comprehensions,
    ```
    [x for x in range(1, 8) if x % 2 != 0]
    ```
    mymusise
        17
    mymusise  
    OP
       2017 年 2 月 9 日
    @ChefIsAwesome 恩恩,看了下它的源码的确是维护着 lineage
    mymusise
        18
    mymusise  
    OP
       2017 年 2 月 9 日
    @ik @silymore 啊哈,没懂什么意思,暗号么?
    czheo
        19
    czheo  
       2017 年 2 月 9 日
    @mymusise 等你发 PR 呢,没收到啊
    ik
        20
    ik  
       2017 年 2 月 10 日 via iPhone
    @mymusise 不不不,没有二维码就好,哈哈
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4398 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 07:09 PVG 15:09 LAX 23:09 JFK 02:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86