Python生成器
Python生成器generator是一个有意思的东西,首先来看:
通过把列表生产式的[ ]改为(),弄了一个生成器出来,不用生成很占内存的列表了,在有些情况下就可以这样使用,但是,一旦采用list()方法或者for循环提取值或者next()方法,生成器里的值就会不断减少,就像出栈那样
再来看下面的这个:
def MyGenerator(): print('aaa') for n in range(1, 4): print('hhh') yield n return "done" print(MyGenerator) Num=MyGenerator() print(Num) print(next(Num)) print(next(Num)) print(next(Num))
结果:
<function MyGenerator at 0x0000017DC6D240D0> <generator object MyGenerator at 0x0000017DC6CFDB48> aaa hhh 1 hhh 2 hhh 3
前两个print没问题,一个是函数,后面是生成器对象(实例)
第一个next(Num)说明生成器一开始启动之后,执行了打印'aaa',然后打印'hhh',然后到yield的地方就跳出,把yield后面那个n(n此时是1)返回并打印;
第二个next(Num)说明生成器并不会去打印'aaa'了,而是会打印'hhh',并且把n(此时是2)返回并打印;第三个 next(Num)同理
由上我们可以知道next函数会启动生成器,并且得到yield后面的值,但并不是让生成器从头开始执行,而是到yield那个地方执行,包括for循环在内。
作一个变化:
def MyGenerator(): print('aaa') for n in range(1, 4): print('hhh') yield n print('bbb') return "done" #print(MyGenerator) Num=MyGenerator() #print(Num) #while True: #print(next(Num)) #print(next(Num)) print(next(Num))
aaa
hhh
1
接着连续打印两个:
def MyGenerator(): print('aaa') for n in range(1, 4): print('hhh') yield n print('bbb') return "done" #print(MyGenerator) Num=MyGenerator() #print(Num) #while True: #print(next(Num)) print(next(Num)) print(next(Num))
aaa hhh 1 bbb hhh 2
代码在第一个的基础上多执行了打印'bbb','hhh'和2,说明了什么,说明第二个next启动时,代码从上次执行完的地方开始向下执行,并且总是到yield的地方就把n抛出,不再执行下去
接着再做一次变化:
def MyGenerator(): print('aaa') for n in range(1, 4): print('hhh') yield n print('bbb') return "done" #print(MyGenerator) Num=MyGenerator() #print(Num) for i in range(4): print('这是第%d次哦'%i) print(next(Num))
这是第0次哦 aaa hhh 1 这是第1次哦 bbb hhh 2 这是第2次哦 bbb hhh 3 这是第3次哦 bbb Traceback (most recent call last): File "D:\Postgraduate\Python\Python爬取美国商标局专利\异步爬虫\yield_consumer_producer.py", line 56, in <module> print(next(Num)) StopIteration: done
明白了吧,当i=3的时候,也就是第四次启动next进入生成器,接着上一次代码停止的地方也就是yield n(n是3),然后执行打印'bbb',但是生成器里的for循环已经结束了,yield没有可以抛出去的东西了,所以报错了
再来看下面的代码:
import time def func(n): for i in range(0, n): arg = yield i #arg = 3 print('func:',arg) f = func(3) for i in range(4): print('这是第%d次哦'%i) print('main_next:', next(f)) #print('main_send:', f.send(100)) time.sleep(0.1)
这是第0次哦 main_next: 0 这是第1次哦 func: None main_next: 1 这是第2次哦 func: None main_next: 2 这是第3次哦 func: None Traceback (most recent call last): File "D:\Postgraduate\Python\Python爬取美国商标局专利\异步爬虫\yield_consumer_producer.py", line 35, in <module> print('main_next:', next(f)) StopIteration
过程:第0次启动next,进入生成器函数,yield 抛出0,第1次启动next,进入生成器执行上次代码yield之后的部分,也就是arg=,这部分,这也是关键所在,而arg传入的值是什么呢,不是yield i 这个东西,而是next传进去的东西,而next传入的是None,因此打印出来就是None,接着生成器函数继续执行,i是1了,到了yield的地方,继续把i抛出,给next,把main_next打印出来;同理,接下去第2次启动next。。直到生成器的for循环结束,yield没有东西可以抛,就出现异常了
紧接着上面的代码,我们把send加进去看看
import time def func(n): for i in range(0, n): arg = yield i #arg = 3 print('func:',arg) f = func(3) for i in range(4): print('这是第%d次哦'%i) print('main_next:', next(f)) print('main_send:', f.send(100)) time.sleep(0.1)
这是第0次哦 main_next: 0 func: 100 main_send: 1 这是第1次哦 func: None main_next: 2 func: 100 Traceback (most recent call last): File "D:\Postgraduate\Python\Python爬取美国商标局专利\异步爬虫\yield_consumer_producer.py", line 36, in <module> print('main_send:', f.send(100)) StopIteration
我们来分析一下过程:
第一次启动next,进入生成器函数,i是0,yield抛出i,打印main_next:0;
第一次启动send,紧接着上次代码挂起的地方,把send的参数100给yield i(这里非常关键),也就是说arg被赋值为100,接下去打印func:100,并接着执行当i是1的时候,yield抛出i,给send,打印main_send:1;
第二次启动next,进入生成器函数,这时执行arg=这条语句,但是next传进去的参数是None,于是func打印出None,接着生成器函数继续执行下去,i是2了,yield抛出2给next,打印main_next:2;
第二次启动send,进入生成器函数,这时执行arg=这条语句,把send的参数100给arg,打印func:100
关键之处就在于arg = yield这条语句的理解以及send(100)里面的参数传递问题
注意的地方:第一次启动生成器最好用next,不要用send,更不要用带参数的send,那就直接报错了
import time def func(n): for i in range(0, n): arg = yield i #arg = 3 print('func:',arg) f = func(3) for i in range(4): print('这是第%d次哦'%i) #print('main_next:', next(f)) print('main_send:', f.send(100)) time.sleep(0.1)
yield from就是为了让yield更好地使用,避免报错,且容易得到抛出的值
import time def func(n): yield from range(0,n) f = func(3) print(list(f)) f = func(3) for g in f: print(g)
[0, 1, 2]
0
1
2
注意:第一次list执行之后,生成器里就没有东西了