Jusene's Blog

Python生成器总结

字数统计: 1.6k阅读时长: 8 min
2017/10/12 Share

生成器

生成器不会把结果保存在一个列表中,而是保存为生成器状态,而后在每次迭代的时候返回一个值,直到迭代完返回StopIteration异常结束。生成器实现可以很好的解决,当列表过长耗费内存等情况。

生成器表达式

生成器表达式与列表解析式相同,只不过是将[]换成了()。

1
# 列表解析式
2
>>> [i**2 for i in range(100)]
3
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801]
4
5
# 生成器解析式
6
>>> (i**2 for i in range(100))
7
<generator object <genexpr> at 0x107b0bf68>   #返回迭代器
8
>>> gen=(i**2 for i in range(100))
9
>>> next(gen)
10
0
11
>>> next(gen)
12
1
13
>>> for i in gen:
14
...     print(i,end='\t')
15
... 
16
4	9	16	25	36	49	64	81	100	121	144	169	196225	256	289	324	361	400	441	484	529	576	625	676	729784	841	900	961	1024	1089	1156	1225	1296	1369	1444	1521	1601681	1764	1849	1936	2025	2116	2209	2304	2401	2500	2601	2704	2802916	3025	3136	3249	3364	3481	3600	3721	3844	3969	4096	4225	4354489	4624	4761	4900	5041	5184	5329	5476	5625	5776	5929	6084	6246400	6561	6724	6889	7056	7225	7396	7569	7744	7921	8100	8281	8468649	8836	9025	9216	9409	9604	9801

生成器函数

生成器函数,在函数定义是通过yield来实现。

1
#实现一个阶乘的生成器
2
>>> def Factorial():
3
...     ret=1
4
...     incr=1
5
...     while True:
6
...             yield ret
7
...             incr += 1
8
...             ret *= incr
9
>>> gen=Factorial()
10
>>> gen
11
<generator object Factorial at 0x107b0bf68>
12
>>> type(gen)
13
<class 'generator'>
14
>>> next(gen)    =>  1的阶乘
15
1
16
>>> next(gen)    =>  2的阶乘
17
2
18
>>> next(gen)    =>  3的阶乘
19
6
20
>>> next(gen)    =>  4的阶乘
21
24
22
>>> next(gen)    =>  5的阶乘
23
120
24
25
#从列表中读取生成器数据
26
>>> lst=[1,2,3,4,5]
27
>>> def gen():
28
...     yield from lst
29
... 
30
>>> gen=gen()
31
>>> gen
32
<generator object gen at 0x107b46360>
33
>>> for i in gen:
34
...     print(i)
35
... 
36
1
37
2
38
3
39
4
40
5

yield 与 return

yield可以理解为不停的暂停函数的执行,而return则是直接退出函数的执行,我们可以看下yield和return的效果。

1
>>> def Incr():
2
...     for i in range(100):
3
...             yield i
4
...             if i >= 10:
5
...                     return 'more than 10'   
6
... 
7
>>> gen=Incr()
8
>>> gen
9
<generator object Incr at 0x107b46048>
10
>>> for i in gen:
11
...     print(i)
12
... 
13
0
14
1
15
2
16
3
17
4
18
5
19
6
20
7
21
8
22
9
23
10                     
24
25
# 这里被函数中的return退出了生成器函数,但是我们并没有看见return回的字符,而且这里我们也可以得到结论,只要有yield,整个函数就是生成器函数,返回的就是迭代器。
26
# 我们手动来查找下return回的字符在哪
27
28
>>> gen=Incr()
29
>>> next(gen)
30
0
31
>>> next(gen)
32
1
33
>>> next(gen)
34
2
35
>>> next(gen)
36
3
37
>>> next(gen)
38
4
39
>>> next(gen)
40
5
41
>>> next(gen)
42
6
43
>>> next(gen)
44
7
45
>>> next(gen)
46
8
47
>>> next(gen)
48
9
49
>>> next(gen)
50
10
51
>>> next(gen)
52
Traceback (most recent call last):
53
  File "<stdin>", line 1, in <module>
54
StopIteration: more than 10            #这里return回的字符被作为了异常的一部分

生成器支持的方法

1
>>> help(gen)
2
Help on generator object:
3
4
Incr = class generator(object)
5
 |  Methods defined here:
6
 ...
7
 |  close(...)
8
 |      close() -> raise GeneratorExit inside generator.
9
 |  
10
 |  send(...)
11
 |      send(arg) -> send 'arg' into generator,
12
 |      return next yielded value or raise StopIteration.
13
 |  
14
 |  throw(...)
15
 |      throw(typ[,val[,tb]]) -> raise exception in generator,
16
 |      return next yielded value or raise StopIteration.
17
 ...

close()

手动关闭生成器函数。

1
>>> gen=Incr()
2
>>> next(gen)
3
0
4
>>> next(gen)
5
1
6
>>> gen.close()
7
>>> next(gen)
8
Traceback (most recent call last):
9
  File "<stdin>", line 1, in <module>
10
StopIteration

send()

生成器函数还可以接受外部传入参数,并根据参数来进行结果返回,并且这里也是可以实现最简单的协程(协程可以理解为用户空间控制的程序调度),下面是例子:

1
>>> def gen1():
2
...     print('gen1 is running...')
3
... 
4
>>> def gen2():
5
...     val='starting...'
6
...     while True:
7
...             recv=yield val
8
...             if recv == 'gen1':
9
...                     gen1()
10
...             val='get value {}'.format(recv)
11
... 
12
>>> gen=gen2()
13
>>> gen
14
<generator object gen2 at 0x107b460a0>
15
>>> print(gen.send(None))
16
starting...
17
>>> print(gen.send('test'))
18
get value test
19
>>> print(gen.send('gen1'))
20
gen1 is running...
21
get value gen1

生成器函数可以通过gen.send(None)或者next(gen)来启动生成器函数,执行到第一次yield暂停,返回初始值,接下来的参数可以通过send发送参数并赋值给recv,并通过下面的表达式改变val的赋值。

throw()

向生成器函数送入一个异常,可以自定义一些异常处理。

1
>>> def thro():
2
...     while True:
3
...             try:
4
...                     yield "test value 1"
5
...                     yield "test value 2"
6
...                     print("it is ok")
7
...             except ValueError:
8
...                     print("we get a ValueError")
9
...             except IOError:
10
...                     break
11
>>> gen=thro()
12
>>> gen
13
<generator object thro at 0x107b46360>
14
>>> next(gen)
15
'test value 1'
16
>>> next(gen)
17
'test value 2'
18
>>> next(gen)
19
it is ok
20
'test value 1'
21
>>> gen.throw(ValueError)
22
we get a ValueError
23
'test value 1'
24
>>> gen.throw(ValueError)
25
we get a ValueError
26
'test value 1'
27
>>> gen.throw(IOError)
28
Traceback (most recent call last):
29
  File "<stdin>", line 1, in <module>
30
StopIteration
31
>>> next(gen)
32
Traceback (most recent call last):
33
  File "<stdin>", line 1, in <module>
34
StopIteration

当throw传入异常的时候,触发了except代码块,而后重新返回try执行第一个yield,所以会出现我们传入异常的时候始终出现的都是相同的结果的原因。

总结

1.生成器就是一种迭代器
2.第一次执行生成器,执行完yield,函数会被挂起,所有的参数和状态会被保存,当再次执行生成器,生成器会从挂起的地方往后接着执行,直到StopIteration。
3.send传入生成器,这是最简单的协程模型。
4.next()等价于send(None)

CATALOG
  1. 1. 生成器
  2. 2. 生成器表达式
  3. 3. 生成器函数
  4. 4. yield 与 return
  5. 5. 生成器支持的方法
    1. 5.1. close()
    2. 5.2. send()
    3. 5.3. throw()
  6. 6. 总结