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
aragakiiyui
V2EX  ›  Python

大吃一惊! 函数里面写生成器解析(生成器表达式),居然会这样.....

  •  
  •   aragakiiyui · 2017-03-16 11:16:41 +08:00 · 5086 次点击
    这是一个创建于 2570 天前的主题,其中的信息可能已经有所发展或是发生改变。

    make_index 这个函数就是简单的做一个倒排索引,将出现了某种语言的文章与该语言关联起来,返回的结果是一个列表,列表中每个元素是一个 tuple 。

    from collections import namedtuple
    
    WikipediaArticle = namedtuple("WikipediaArticle", ["title", "text"])
    
    
    def make_index(langs, articles):
        result = []
        for lang in langs:
            # 创建包含该 lang 的文章的生成器
            article_gen = (article for article in articles if article.text.find(lang) >= 0) 
            result.append((lang, article_gen))
        return result
    
    if __name__ == '__main__':
        articles = [
            WikipediaArticle('1', "Groovy is pretty interesting, and so is Erlang"),
            WikipediaArticle('2', "Scala and Java run on the JVM"),
            WikipediaArticle('3', "Scala is not purely functional"),
            WikipediaArticle('4', "The cool kids like Haskell more than Java"),
            WikipediaArticle('5', "Java is for enterprise developers")
        ]
    	langs = ["Scala", "Java", "Groovy", "Haskell", "Erlang"]
        for item in make_index(langs, articles):
            print(item[0], list(item[1])
            
    

    然后跑出来的结果是:

        Scala [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
        Java [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
        Groovy [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
        Haskell [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
        Erlang [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
    

    很明显这结果有问题,取得都是第一条数据, 但是如果在函数内部做一点修改,会得到以下结果:

    # result.append((lang, article_gen)) 这一句改成 result.append((lang, list(article_gen)))
    ('Scala', [WikipediaArticle(title='2', text='Scala and Java run on the JVM'), WikipediaArticle(title='3', text='Scala is not purely functional')])
    ('Java', [WikipediaArticle(title='2', text='Scala and Java run on the JVM'), WikipediaArticle(title='4', text='The cool kids like Haskell more than Java'), WikipediaArticle(title='5', text='Java is for enterprise developers')])
    ('Groovy', [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')])
    ('Haskell', [WikipediaArticle(title='4', text='The cool kids like Haskell more than Java')])
    ('Erlang', [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')])
    

    感觉好奇怪,为什么会这样,求解, 在函数里面解开生成器和在函数外面解开有什么区别吗?

    第 1 条附言  ·  2017-03-16 12:54:30 +08:00

    我把return换成yield之后,又能得到预期的结果

    def make_index(langs, articles):
        for lang in langs:
            article_gen = (article for article in articles if article.text.find(lang) >= 0)
            yield lang, article_gen
    
    for item in make_index(langs, articles):
           print(item, list(item[1]))
    
    
    输出:
    ('Scala', <generator object make_index.<locals>.<genexpr> at 0x00EFBDB0>) [WikipediaArticle(title='2', text='Scala and Java run on the JVM'), WikipediaArticle(title='3', text='Scala is not purely functional')]
    ('Java', <generator object make_index.<locals>.<genexpr> at 0x00F091E0>) [WikipediaArticle(title='2', text='Scala and Java run on the JVM'), WikipediaArticle(title='4', text='The cool kids like Haskell more than Java'), WikipediaArticle(title='5', text='Java is for enterprise developers')]
    ('Groovy', <generator object make_index.<locals>.<genexpr> at 0x00EFBDB0>) [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
    ('Haskell', <generator object make_index.<locals>.<genexpr> at 0x00F091E0>) [WikipediaArticle(title='4', text='The cool kids like Haskell more than Java')]
    ('Erlang', <generator object make_index.<locals>.<genexpr> at 0x00EFBDB0>) [WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')]
    
    第 2 条附言  ·  2017-03-16 13:43:21 +08:00

    问题解决了,大概就是一个和 https://docs.python.org/3.6/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result 类似的问题,for循环结束后,迭代器中绑定的lang值都执行了langs这个列表中最后一个元素(Erlang), 所以导致所有迭代器的结果都是Erlang的结果。

    解决这个问题的办法可以参照@siteshen说的。

    def make_index(langs, articles):
        res = []
        for lang in langs:
            res.append((lang, (lambda arg: (article for article in articles if article.text.find(arg) >= 0))(lang)))
        return res
    

    感谢各位大神解惑!~

    28 条回复    2017-03-17 09:59:26 +08:00
    a87150
        1
    a87150  
       2017-03-16 11:50:13 +08:00   ❤️ 1
    aragakiiyui
        2
    aragakiiyui  
    OP
       2017-03-16 12:01:16 +08:00
    @a87150 大哥,你有认真看我的描述吗,我看你发的链接是教程,完全没解答问题呢。希望能给建设性的指教。
    bxb100
        3
    bxb100  
       2017-03-16 12:04:05 +08:00
    兄弟
    ```
    [article for article in articles if article.text.find(lang) >= 0]
    ```
    boluoshu
        4
    boluoshu  
       2017-03-16 12:08:21 +08:00
    居然不是震惊....
    bxb100
        5
    bxb100  
       2017-03-16 12:12:19 +08:00
    不过输出成这样有点奇怪
    按理说不该是<generator object...>吗
    觉得该是 print 的锅
    mink
        6
    mink  
       2017-03-16 12:15:44 +08:00
    为什么我试了一下没问题呢
    ```
    WikipediaArticle(title='2', text='Scala and Java run on the JVM')
    WikipediaArticle(title='2', text='Scala and Java run on the JVM')
    WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')
    WikipediaArticle(title='4', text='The cool kids like Haskell more than Java')
    WikipediaArticle(title='1', text='Groovy is pretty interesting, and so is Erlang')
    ```
    bxb100
        7
    bxb100  
       2017-03-16 12:19:26 +08:00
    @mink 怎么输出成这样的......
    mink
        8
    mink  
       2017-03-16 12:22:19 +08:00
    @bxb100 还是有问题, 我测试错了
    ```python
    article_gen = (article for article in articles if article.text.find(lang) >= 0)
    print(lang, next(article_gen))
    result.append((lang, article_gen))
    ```
    我实在里面试了一下, 最后输出的还是跟楼主发的一样。
    a87150
        9
    a87150  
       2017-03-16 12:27:09 +08:00
    @aragakiiyui 这么短一个程序有混用 tab 和空格,少括弧,先生成 tuple 又莫明其妙转换成 list 这么多错误,所以叫你学好基础知识。
    misaka19000
        10
    misaka19000  
       2017-03-16 12:27:46 +08:00 via Android
    楼主明天就来 UC 上班,待遇从优
    bxb100
        11
    bxb100  
       2017-03-16 12:29:53 +08:00
    25 87 SETUP_LOOP 50 (to 140)
    90 LOAD_GLOBAL 1 (make_index)
    93 LOAD_FAST 1 (langs)
    96 LOAD_FAST 0 (articles)
    99 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
    102 GET_ITER
    >> 103 FOR_ITER 33 (to 139)
    106 STORE_FAST 2 (item)

    26 109 LOAD_GLOBAL 2 (print)
    112 LOAD_FAST 2 (item)
    115 LOAD_CONST 16 (0)
    118 BINARY_SUBSCR
    119 LOAD_GLOBAL 3 (list)
    122 LOAD_FAST 2 (item)
    125 LOAD_CONST 17 (1)
    128 BINARY_SUBSCR
    129 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
    132 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
    135 POP_TOP
    136 JUMP_ABSOLUTE 103
    >> 139 POP_BLOCK
    >> 140 LOAD_CONST 0 (None)
    143 RETURN_VALUE
    bxb100
        12
    bxb100  
       2017-03-16 12:31:08 +08:00
    寄存器的锅?求大神解释.....
    imn1
        13
    imn1  
       2017-03-16 12:33:16 +08:00
    生成器只能读取一次,自己想想逻辑哪里有问题吧
    提示: list(<generator object>) in for
    ArieShout
        14
    ArieShout  
       2017-03-16 12:42:29 +08:00 via iPhone   ❤️ 2
    局部变量 lang 被所有生成器表达式捕获并共享,表达式被遍历输出的时候才会延迟获取 lang 的值,此时循环已经结束, lang 获取的是最语言列表的最后一个值
    siteshen
        15
    siteshen  
       2017-03-16 12:44:56 +08:00   ❤️ 1
    原因: generator 在取 next 时才去执行的代码,执行代码时 lang 的值是最后一次的值,可以改成这样看看效果:
    article_gen = ((lang, article) for article in articles if article.text.find(lang) >= 0) 这里返回的 lang 就是最后一个值。

    暂时没想到更好的在循环里生成 generator 的办法,我会避免使用。参照之前经典的 js 面试题改了下代码:
    article_gen = (lambda l: (article for article in articles if article.text.find(l) >= 0))(lang)
    aragakiiyui
        16
    aragakiiyui  
    OP
       2017-03-16 12:48:57 +08:00
    @bxb100 我用的不是列表解析啊,用的是生成器表达式,如果只是 print 生成器,就是 generator objec , 但是我 print 的时候把他转成列表了。
    所有我还是不明白为什么,转成列表之后,就只剩一个元素了。
    aragakiiyui
        17
    aragakiiyui  
    OP
       2017-03-16 12:52:19 +08:00
    @a87150 不好意思,可能是我 markdown 没写好,但是我代码都是 pep8 检测了的,不可能会有什么语法上的错误或者多括号少括号的问题,至于你说的生成 tuple 和 list ,我觉得你可能没看懂代码。转成 list 的原因是要把生成器的元素释放出来。
    zhy0216
        18
    zhy0216  
       2017-03-16 13:00:15 +08:00
    原因是变量的作用域问题

    gen_generator = lambda lang, articles: (article for article in articles if article.text.find(lang) >= 0)

    in loop:

    article_gen = gen_generator(lang, articles)
    aragakiiyui
        19
    aragakiiyui  
    OP
       2017-03-16 13:01:19 +08:00
    @imn1 可是我的结果是五个 ( String , generator ) tuple :
    ('Scala', <generator object make_index.<locals>.<genexpr> at 0x0061BDB0>)
    ('Java', <generator object make_index.<locals>.<genexpr> at 0x006291E0>)
    ('Groovy', <generator object make_index.<locals>.<genexpr> at 0x0061BDB0>)
    ('Haskell', <generator object make_index.<locals>.<genexpr> at 0x006291E0>)
    ('Erlang', <generator object make_index.<locals>.<genexpr> at 0x0061BDB0>)

    然后对每个 tuple 的生成器利用 list 函数,为啥返回的结果是一毛一样的。我就是这点存在疑惑。
    oisc
        20
    oisc  
       2017-03-16 13:05:08 +08:00
    你 article 还是当年的 article , lang 已经不是当年的 lang 了。
    a87150
        21
    a87150  
       2017-03-16 13:09:10 +08:00
    @aragakiiyui 看错了,是无意义的把 list 转换成 list

    list(item[1])
    aragakiiyui
        22
    aragakiiyui  
    OP
       2017-03-16 13:30:34 +08:00
    @a87150 .......item[1]是 generator
    @zhy0216 @ArieShout @oisc 嗯,我图形化代码的时候,发现所有的 generator 都指向了最后那个 lang 为 Erlang 的结果。 看了确实是 article 没变, lang 却变了。谢谢你们,我大概知道出错的原因了。但是还是不太理解里面的过程,请问哪里可以多了解这方面的知识?~
    a87150
        23
    a87150  
       2017-03-16 13:46:09 +08:00
    @aragakiiyui 你真傻还是装傻啊

    result.append((lang, list(article_gen)))

    你说 item[1]是不是 generator
    zhy0216
        24
    zhy0216  
       2017-03-16 14:19:48 +08:00
    @aragakiiyui
    Finding closure with closures :
    aragakiiyui
        25
    aragakiiyui  
    OP
       2017-03-16 15:48:42 +08:00
    @a87150 你是这里找到的吧 # result.append((lang, article_gen)) 这一句改成 result.append((lang, list(article_gen)))
    这里是为了说明问题所做的比较啊大哥....关注点也是清奇。

    所以请好好审题。明确别人问的是什么。然后丢合适的链接。要么就不答,要么就认真答。大家都是奔着解决问题去的。你这种回答态度很让人不舒服。 还好其他 v 友读懂了题目,给我上了一课。
    falseen
        26
    falseen  
       2017-03-16 16:07:18 +08:00 via Android   ❤️ 1
    aragakiiyui
        27
    aragakiiyui  
    OP
       2017-03-16 16:38:57 +08:00
    @falseen 确实能找到 @a87150 不好意思,自己打脸了,我这下真心诚意接收你的建议,补基础去。收回前面的话~ sorry!!!
    WKPlus
        28
    WKPlus  
       2017-03-17 09:59:26 +08:00
    这是闭包的一个容易误解的地方嘛,等你把 generator 转成 list 的时候, generator 的代码开始执行,这时 lang 的值是 Erlang
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4064 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:17 · PVG 13:17 · LAX 22:17 · JFK 01:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.