Python的程序结构[7] -> 生成器/Generator -> 生成器浅析

生成器 / Generator


目录

  1. 关于生成器
  2. 生成器与迭代器
  3. 生成器的建立
  4. 通过迭代生成器获取值
  5. 生成器的 close 方法
  6. 生成器的 send 方法
  7. 生成器的 throw 方法
  8. 空生成器的检测方法

 

1 关于生成器


从计算机科学角度上看,生成器是一种类协程半协程(Semi-coroutine),生成器提供了一种可以通过特定语句或方法来使生成器的执行对象(Execution)暂停,而这语句一般都是 yield。通过  yield 语句将每一次的结果切出执行对象,并带到主线程上来。yield 可以将一个值带出协程,而主线程也可以通过生成器对象的方法将一个值带回生成器的执行对象中去。

在 Python 中生成器常是一个由生成器函数产生的对象,可以使用 nex t函数调用其内部参数,直到所有参数均被生成返回耗尽,则返回一个 StopIteration 异常。在生成器中,返回参数值使用的不是 return 而是 yield,也正是这个 yield 使其所在的函数变成了一个生成器。

对于生成器来说,它不会把结果保存在特定序列中,而是将生成器的状态进行保存。每一次 yield 后都会保存生成器的上下文(包括内部状态及变量值),当下次迭代到来时,则从 yield 处继续生成器的后续函数,且内部状态均为上次 yield 前的状态。

2 生成器与迭代器


而对于生成器与迭代器的关系,可以说每一个生成器都是一个迭代器,反之不亦然。也可以认为,生成器是一个带有迭代器特性,并且具有一个或多个 yield 表达式对象

通过源码可以查看到,generator 的基类为 iterator。

3 生成器的建立


生成器的建立主要有以下两种:

1. 通过函数中的 yield 表达式,挂起函数;

2. 对于可迭代对象,使用[]推导时会遍历可迭代对象产生一个列表,而使用()推导时,会返回可迭代的对象作为一个生成器

关于生成器的生成,可以使用以下两种方式,即分别使用()推导和 yield 进行生成

 1 # Use () to replace [], make it into a generator
 2 gen = (x**2 for x in range(7))
 3 lis = [x**2 for x in range(7)]
 4 print(type(gen), type(lis))
 5 
 6 # Define function to create generator by using yield
 7 # Return will cause StopIertation and return value will be present as an explaination for StopIteration
 8 def gener(m):
 9     n = 1
10     while n < m:
11         yield n
12         n += 1
13         if n == 3:
14             return 'Stop at 3'
15 
16 # gener is function, while gener(7) is generator
17 print(type(gener), type(gener(7)))

输出结果为

<class 'generator'> <class 'list'>  
<class 'function'> <class 'generator'>  

通过输出的第一行结果可以看到,通过[]推导得到的是一个 list ,而通过 ()推导得到的是一个生成器类,即返回的是生成器对象。

而对于 yield 方式生成的生成器,可以看到,生成生成器的 gener 是一个函数,而调用函数之后返回的结果 gener(m) 则是一个生成器。

同时,还可以通过 help 查看生成器内部的一些信息,

1 help(gener(10))  

输出结果

Help on generator object:

gener = class generator(object)
 |  Methods defined here:
 |  
 |  __del__(...)
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  close(...)
 |      close() -> raise GeneratorExit inside generator.
 |  
 |  send(...)
 |      send(arg) -> send 'arg' into generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  throw(...)
 |      throw(typ[,val[,tb]]) -> raise exception in generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  gi_code
 |  
 |  gi_frame
 |  
 |  gi_running
View Code

对于生成器来说,内部包含了许多内置方法,包括 iter() 会调用的 __iter__ 函数,next() 会调用的 __next__ 函数,这使得生成器遵循迭代器协议,以及3个可以直接调用的方法 close(),  send(),  throw()。

4 通过迭代生成器获取值


对于生成器的迭代可以由两种方式,第一种是使用 for 循环迭代,另一种是使用 next 获取下一个生成值,

 1 # Iterate the generator
 2 # Use "for"
 3 for i in gener(7):
 4     print(i)
 5 g = gener(7)
 6 # Use "next"
 7 while g:
 8     try:
 9         print(next(g))
10     except StopIteration as e:
11         print(e)
12         break

输出结果

1  
2  
1  
2  
Stop at 3  

此处结果可以看出,两种方式均成功让生成器运作,且在迭代到 3 时,生成器内部的 return 语句会抛出一个 StopIteration 异常,并且将返回值作为对异常的说明,可以通过异常捕获进行查看。

5 生成器的 close 方法


使用 close 方法可以将生成器进行关闭,并且后续的迭代操作将返回一个 StopIteration 的异常。

 1 # Test close() function: close function will cause later iteration call a StopIteration
 2 def gx():
 3     yield 1
 4     yield 2
 5     yield 3
 6     
 7 g = gx()
 8 for i in range(3):
 9     try:
10         c = next(g)
11         print(c)
12     except StopIteration:
13         print('Stop at %d' % c)
14     if c == 2:
15         g.close()

在 gx() 生成器中,会 yield 三个值,而在循环中,当调用 2 次 next() 函数后,便关闭生成器,同时对异常进行了捕获,最终查看结果

1
2
Stop at 2

可以看到生成器由于被 close() 方法关闭,最终停在了 2 处,第三次迭代并未成功。

6 生成器的 send 方法


首先定义的 gx() 函数会进入一个循环,每次 yield 之后,会将 send 函数传送的值赋值给 r。值得注意的是:

1. yield 实际上是会返回一个值的,当使用 next() 时,yield 返回的是一个 None 值;

2. 当向初次启动的生成器发送参数时(在此之前未对生成器进行过任何迭代,包括 next() 函数等),第一次 send() 发送的参数必须为 None,其原因在于,由于生成器在 send() 之前未被启动,因此当调用 send() 方法时,会启动生成器,此时挂起在 yield 处,当再次调用下一个 send() / next() 函数时, yield 才会将收到的值传入,而到此时为止,第一次 send() 函数发送的值都没有能够执行赋值或其他操作。因此便将 send(None) 作为第一次生成器启动的标志。而 next 函数由于原本就传入的是 None,因此可以启动生成器。

Note: 实质上生成器的 __next__() 函数调用的是 send(None) 函数。

下面的函数中,第一次传入非 None 参数将无法启动生成器,并且之后的所有非 None 参数均无法启动,只有当第一个 None 被传入后生成器才会开始运行,之后的每一个 None 都会被当成正常参数传入。

 1 # Test send() function: send function will pass a value to generator
 2 def gx():
 3     i = 'Init'
 4     while True:
 5         r = yield i
 6         if r == 'Stop':
 7             print('Got: Stop, stop at here')
 8             break
 9         i = 'Got: %s' % r
10 g = gx()
11 for i in [0, None, None, 'First', 'Second', 'Third', 'Stop']:
12     try:
13         print(g.send(i))
14     except TypeError as e:
15         print('Send "%s" into g, and cause TypeError: %s.' % (i, e))
16     except StopIteration:
17         print('Send "%s" into g, and cause StopIteration.' % i)

输出结果

Send "0" into g, and cause TypeError: can't send non-None value to a just-started generator.  
Init  
Got: None
Got: First  
Got: Second  
Got: Third  
Got: Stop, stop at here  
Send "Stop" into g, and cause StopIteration. 

查看最终的结果可以发现,其工作过程为,首先传入的非 None 参数直接引起了生成器的 TypeError 异常,而随后的 send(None) 启动了生成器,第一次 yield 的参数 Init 被传出,但是传入的 None 未被使用,挂起在yield处且未进行赋值给r的操作,这时再次调用一个 send(None),此时传入的 None 会被作为参数赋给 r,并且执行下面的操作,改变 i 后执行 yield i 并挂起,等待下一个 send() / next()。

7 生成器的 throw 方法


throw() 方法可以向生成器内部传送一个异常,会将当前挂起处的 yield 消耗掉,若没有异常捕获的存在,则会引发生成器异常,若存在异常捕获则会进入 except 执行异常处理后继续程序运行直到下一个 yield 处等待。若无下一个 yield 则结束生成器。

 1 def gx():
 2     c = 1
 3     while True:
 4         try:
 5             print('First yield')
 6             yield c
 7             c += 1
 8             print('Second yield')
 9             yield c
10             c += 1
11             print('Third yield')
12             yield c
13             c += 1
14         except ValueError:
15             print('Receive an ValueError, and c is %d now.' % c)
16         except TypeError:
17             print('Receive a TypeError, and c is %d now.' % c)
18             break
19 g = gx()
20 print('First time using next(g) got:', next(g), '
')
21 print('Second time using g.throw() got:', g.throw(ValueError), '
')
22 print('Thrid time using next(g) got:', next(g), '
')
23 print('Forth time using g.throw() got:',g.throw(TypeError), '
')

此处的第一个 next() 函数从 yield 处得到了 1,而第二次使用了 throw() 函数,传入了一个 ValueError ,此时挂起的第二个 yield c 将不会 yield,且由异常捕获而进入 except 语句中,执行异常捕获处理后再次回到循环挂起在第一个 yield c 处。随后正常调用,最后由 TypeError 结束循环。此时由于挂起的 yield 没有返回值且后续无 yield,因此抛出了 StopIteration 异常。

最终输出为

First yield
First time using next(g) got: 1 

Receive an ValueError, and c is 1 now.
First yield
Second time using g.throw() got: 1 

Second yield
Thrid time using next(g) got: 2 

Receive a TypeError, and c is 2 now.
Traceback (most recent call last):
  File "C:UsersEKELIKEDocumentsPython Note3_Program_Structure3.7_Special_Structuregenerator_demo.py", line 91, in <module>
    print('Forth time using g.throw() got:',g.throw(TypeError), '
')
StopIteration

8 空生成器的检测方法


在使用生成器时,通常会遇到一些情况,想要对当前的生成器进行判断,看生成器是否为空,从而根据生成器是否有内容来执行不同的操作。但是,此时会遇到一个问题,即如何得知生成器是否为空。通常会有以下几种思路,但这几种思路都不能很好的完成需求,

1. 直接 print 生成器 – 此时并不会显示出生成器的内容,而是显示这是一个 generator obj;

2. 通过迭代获取内部值看是否为空 – 迭代之后原生成器的值将被消耗,此时虽然能够判断生成器是否为空,但却已经消耗了生成器内部的值,甚至迭代完成后只留下一个空生成器;

3. 使用 list/tuple 等函数强制转换生成器为其他类型的数据结构,再对转换后的新数据进行操作。但这种方式若是在生成器包含数据量巨大时,生成的新数据结构将会占用很大的空间,此时便失去了使用生成器的意义。

参考了 Stack Overflow 上的一个解答,利用装饰器对生成器进行装饰后可以使得生成器在为空的时候返回 None 值

 1 """
 2 Decorator for generator empty test
 3 """
 4 import itertools
 5 
 6 def deco(f):
 7     def wap(*args):
 8         it = f(*args)
 9         try:
10             first = next(it)
11         except StopIteration:
12             return None
13         return itertools.chain([first], it)
14     return wap
15 
16 @deco
17 def gen(g):
18     for i in g:
19         yield i
20 
21 
22 x = gen([1, 2])
23 print("Generator is:", x)
24 for i in x:
25     print("Generator element:", i)
26 x = gen([])
27 print("Generator is:", x)

上面的装饰器函数 deco 首先获取生成器的第一个元素值,然后判断是否获取成功,若获取成功则将第一个值添加回原生成器中,再将新生成器返回,若获取失败则说明原来的生成器为空,此时返回 None 值。

查看运行输出的结果,可以看到,当生成器没有值时,此时能够返回 None。

Generator is: <itertools.chain object at 0x02EA9DD0>  
Generator element: 1  
Generator element: 2  
Generator is: None  

相关阅读


1. 迭代器

2. 装饰器

参考链接


http://www.jianshu.com/p/b709747d125e

https://*.com/questions/2776829/difference-between-pythons-generators-and-iterators

http://python.jobbole.com/81911/