gongsui 发表于 2020-11-10 11:39

(python)一直搞不懂yield的用法

本帖最后由 gongsui 于 2020-11-12 10:22 编辑


源码比较长,这里贴关键代码

定义:
def parse_index():
    elements = browser.find_elements_by_css_selector('#index .item .name')
    for element in elements:
      href = element.get_attribute('href')
      yield urljoin(INDEX_URL, href) # 拼接url



调用:
def main():
    try:
for page in range(1, TOTAL_PAGE + 1):
scrape_index(page)
            detail_urls = parse_index()
            for detail_url in list(detail_urls):
logging.info('details urls %s', detail_url)
                scrape_detail(detail_url)
                detail_data = parse_detail()
                logging.info('detail data %s', detail_data)
    finally:
      browser.close()

该如何理解yield 及其调用方法

--------
感谢各位解惑,我暂时把它理解为print一次调用一次yield,先结贴,等以后再深入了解。

the_stars 发表于 2020-11-10 11:46

如果函数中含有yield, 这个函数就是生成器(asnyc 函数是异步生成器), 如果调用这个函数, 会获取这个生成器对象 . 假设是g_obj(注意此时这个函数还没有执行, 一个语句都没有).如果要让这个函数执行. 需要用v = next( g_obj ), 此时这个函数才会被执行, 然后执行到yield的位置停止执行, 将yield的值返回给v, v就获取了那个值. 就可以执行其他的操作. 如果需要继续执行g_obj, 继续next就可以继续上一次的地方继续执行.

the_stars 发表于 2020-11-10 11:50

楼主那个生成器,最多当yield最后一个element后, 如果继续调用next, for循环退出. 函数结束, 那么next()就会抛出一个停止迭代的异常(StopIteration), 我们判断一个生成器已经生成完毕了就是靠这个异常来判断的.
如果用list, tuple作用生成器,那么内部会直接把生成器所有的值求出来, 就是一直调用 next( )获取值放到list或者tuple中,直到生成器yield最后一个元素(raise StopIteration). 其实在这里parse_index yield的所有url而已, 把他们转换成了列表

the_stars 发表于 2020-11-10 11:52

只看源代码, 如果想要体现生成器的价值, 不应该在前面强转列表, 直接这样就可以

for detail_url in parse_index():

nihuge 发表于 2020-11-10 11:53

the_stars 发表于 2020-11-10 11:50
楼主那个生成器,最多当yield最后一个element后, 如果继续调用next, for循环退出. 函数结束, 那么next()就 ...

就相当于一个迭代器对吗

the_stars 发表于 2020-11-10 12:00

nihuge 发表于 2020-11-10 11:53
就相当于一个迭代器对吗

对, 生成器是迭代器的子集

tywolf 发表于 2020-11-10 12:06

yield生成器,最简单的理解方式就是return,但是它又不是一个简单的return,它是会中断循环并保留上一次循环的状态,可以用next、send方法给循环里的变量赋值,你写下面两个Demo就能大概理解了。
例子1:

```python
def foo():
    print("starting...")
    while True:
      res = yield 4
      print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
```

输出结果为:

```python
starting...
4
********************
res: None
4
```

例子2:

```python
def foo():
    print("starting...")
    while True:
      res = yield 4
      print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))
```

输出结果为:

```python
starting...
4
********************
res: 7
4
```

还有在一种用法是在python2里替代for .. in range(),减少内存占用,正常情况下大部分人可能会这样用:
```python
for n in range(1000):
    a=n
```
还有一种办法就是yield:
```python
def foo(num):
    print("starting...")
    while num<1000:
      num=num+1
      yield num
for n in foo(0):
    print(n)
```
其实这种用法在python2里有一个xrang不需要这么麻烦

你写的应该是scrapy,所以用yield结尾跳转到下一步的函数,因为这样才能保存你上一个函数的执行状态以传值,类似于过程式编程,这也是scrapy比较值得推荐的地方。如果自己写虫子,一般都是for几个嵌套来读取列表页最后到详情页,但是用scrapy这种做法就算以后自己看起来代码也会很容易理解

the_stars 发表于 2020-11-10 12:06

生成器可以惰性求值. 只有在需要的时候才去求值. 很多时候是比较高效的.举个例子
比如一个很大的文件.可能上T,   总不能把他们全部都加载到内存把, 先不说内存有没有1个T这么大. 就算有, 加载到内存需要很长时间. 然后加载完毕后, 如果要把它发送出去, 岂不是又要重新for循环, 一段一段的发送出去.既然这样为何读取的时候不 只在内存里面读取一段后, 发送那一段. 在读取下一段. 抛弃上一段的数据. 这样子一下子就可以全部发送出去. 而且占用的内存仅仅是读取那一段的内存而已. 而且那一段内存到底多大我们都可以自己指定.生成器就可以实现这个功能.我们读取一段数据后 就yield出去, 外面把那个数据发送出去后, 我们再继续往下读取下一段yield出去.直到读取完. 生成器结束.
生成器还有其他的方法, 比如send往函数里面发送值. 不过常用的就是生成器这种惰性求值的功能而已.

the_stars 发表于 2020-11-10 12:18

楼上说的那个比较专业:
生成器 它是会中断函数并保存函数当前的状态.当下次执行再次执行那个函数的时候可以从上次暂停的地方继续执行

kesai 发表于 2020-11-10 13:07

yield就相当于与return,只不过是循环按顺序执行到yeild就return出一个值这样就可以生成一个序列了
页: [1] 2
查看完整版本: (python)一直搞不懂yield的用法