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

2017-03-16 11:16:41 +08:00
 aragakiiyui

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')])

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

5118 次点击
所在节点    Python
28 条回复
a87150
2017-03-16 11:50:13 +08:00
aragakiiyui
2017-03-16 12:01:16 +08:00
@a87150 大哥,你有认真看我的描述吗,我看你发的链接是教程,完全没解答问题呢。希望能给建设性的指教。
bxb100
2017-03-16 12:04:05 +08:00
兄弟
```
[article for article in articles if article.text.find(lang) >= 0]
```
boluoshu
2017-03-16 12:08:21 +08:00
居然不是震惊....
bxb100
2017-03-16 12:12:19 +08:00
不过输出成这样有点奇怪
按理说不该是<generator object...>吗
觉得该是 print 的锅
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
2017-03-16 12:19:26 +08:00
@mink 怎么输出成这样的......
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
2017-03-16 12:27:09 +08:00
@aragakiiyui 这么短一个程序有混用 tab 和空格,少括弧,先生成 tuple 又莫明其妙转换成 list 这么多错误,所以叫你学好基础知识。
misaka19000
2017-03-16 12:27:46 +08:00
楼主明天就来 UC 上班,待遇从优
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
2017-03-16 12:31:08 +08:00
寄存器的锅?求大神解释.....
imn1
2017-03-16 12:33:16 +08:00
生成器只能读取一次,自己想想逻辑哪里有问题吧
提示: list(<generator object>) in for
ArieShout
2017-03-16 12:42:29 +08:00
局部变量 lang 被所有生成器表达式捕获并共享,表达式被遍历输出的时候才会延迟获取 lang 的值,此时循环已经结束, lang 获取的是最语言列表的最后一个值
siteshen
2017-03-16 12:44:56 +08:00
原因: 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
2017-03-16 12:48:57 +08:00
@bxb100 我用的不是列表解析啊,用的是生成器表达式,如果只是 print 生成器,就是 generator objec , 但是我 print 的时候把他转成列表了。
所有我还是不明白为什么,转成列表之后,就只剩一个元素了。
aragakiiyui
2017-03-16 12:52:19 +08:00
@a87150 不好意思,可能是我 markdown 没写好,但是我代码都是 pep8 检测了的,不可能会有什么语法上的错误或者多括号少括号的问题,至于你说的生成 tuple 和 list ,我觉得你可能没看懂代码。转成 list 的原因是要把生成器的元素释放出来。
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
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
2017-03-16 13:05:08 +08:00
你 article 还是当年的 article , lang 已经不是当年的 lang 了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/347860

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX