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执行之后,生成器里就没有东西了

 

posted @ 2018-04-08 19:42  嶙羽  阅读(167)  评论(0编辑  收藏  举报