python基础——yield生成器

目录

一、可迭代对象、迭代器和生成器

可迭代对象

迭代器

生成器

生成器的创建方式

生成器的激活/运行方式

生成器的执行状态

向生成器发送消息

二、yield使用

三、send和next函数

四、yield和return对比分析

五、yield总结


一、可迭代对象、迭代器和生成器

可迭代对象

  • 能用for循环进行迭代的对象就是可迭代对象。 # 比如:字符串,列表,元祖,字典,集合等等,都是可迭代对象。
  • 可以用过dir()方法看它里面有__iter__方法,有就是可迭代对象。

迭代器

  • 那么什么叫迭代器呢?它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。
  • 迭代器对象相对可迭代对象多了个__next__方法。所以迭代器是在可迭代的基础上实现的。要创建一个迭代器,我们首先得有一个可迭代对象。

生成器

  • 生成器是在迭代器的基础上,再实现了yield。

生成器的创建方式

列表生成式

from collections.abc import Generator

L = (x^2 for x in range(10))
print(isinstance(L, Generator)) #True

实现yield函数

def foo(n):
	num = 0
	while num < 0:
		yield num
		num += 1

if __name__ == '__main__':
	g = foo(5)
	print(isinstance(g, Generator)) #True

生成器的激活/运行方式

  • 使用next()
  • 使用gengerator.send(None)

生成器的执行状态

  • GEN_CREATED 等待开始执行
  • GEN_RUNNING 解释器正在执行(只有在多线程应用中才能看到这个状态)
  • GEN_SUSPENDED 在yield表达式处暂停
  • GEN_CLOSED 执行结束
from inspect import getgeneratorstate
 
def mygen(n):
    nums = 0
    while nums < n:
        yield nums
        nums += 1
    raise StopIteration  # 注意自己编写生成器时要在结束时抛stop异常
 
if __name__ == '__main__':
    gen = mygen(2)
    print(getgeneratorstate(gen)) #GEN_CREATED
 
    print(next(gen))
    print(getgeneratorstate(gen)) #GEN_SUSPENDED
 
    print(next(gen))
    gen.close()  # 手动关闭/结束生成器
    print(getgeneratorstate(gen)) #GEN_CLOSED

向生成器发送消息

def mygen(n):
    nums = 0
    while nums < n:
        # rec = yield可以接收外部程序通过send()发送的信息,并赋值给rec
        rec = yield nums   # yield nums是将nums  return给外部调用程序。
        if rec is None:
            rec = 1
        nums += rec
    raise StopIteration
 
if __name__=="__main__":
    gen = mygen(10)
    print(next(gen))  # 【坑】注意第一次一定要next或者send(None)来启动
    print(gen.send(2))
    print(next(gen))
    print(gen.send(4))
 
# 输出
0
2
3
7

二、yield使用

首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了:

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

输出结果:

starting...
4
********************
res: None
4

我直接解释代码运行顺序,相当于代码单步调试:

1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,

4.程序执行print("*"*20),输出20个*

5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,

6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

 

三、send和next函数

将上个例子中的最后一行替换成send

def foo():
    print('starting...')
    while True:
        res = yield 4
        print('res:', res)

g = foo()
print(next(g))
print('*'*20)
print(g.send(7))

输出结果:

starting...
4
********************
res: 7
4

注:

  • 无参数的send和next作用一样
  • 第一次调用的send不能带参数

 

四、yield和return对比分析

相同点:都是返回函数执行的结果。

不同点:return在返回结果后结束函数的运行,而yield则是让函数变成一个生成器,生成器每次产生一个值,函数被冻结,被唤醒后再产生一个值。

 

五、yield总结

好处总结:

  1. 不会将所有数据取出来存入内存中;而是返回了一个对象;可以通过对象获取数据;用多少取多少,可以节省内容空间。
  2. 除了能返回一个值,还不会终止循环的运行;
  3. 每次执行到yield,因为底层的实现就是中断的原理,保存栈帧,加载栈帧。
  4. 每次执行结束内存释放,执行的时候占用一点内存,消耗的内存资源就很少

其他补充:

  1. 通常yield都是放在一个函数中,该函数就变成了生成器函数,该函数就变成了一个迭代器
  2. 生成器函数一般都是通过for循环调用,for循环自带next方法
  3. 分布式爬虫会经常使用yield,yield直接放在for循环的内部
  4. 爬虫代码运行时候,for循环自动调用next方法,yield就会不断执行,直到爬取结束
  5. 使用yield也会大大减少爬虫运行时候的内存消耗

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页