1.生成器
列表生成式:
1 l = [i*2 for i in range(10)]
2 print(l)
通过列表生成式,可以直接创建一个列表,但是收到内存限制,列表容量是有限的。如果创建一个包含100w元素的列表,而我们只需要访问前面几个元素,那么就太占用空间了。如果列表元素可以按照某种算法推算出来,我们是否可以在循环过程中不断推算出后续元素呢,这样就不必创建完整的list,从而节省大量的空间,这样一边循环一边计算的机制称为生成器:generator。
1.1 创建生成器
第一种方式:把列表生成式的[]改成()就可以
1 l = [i*2 for i in range(10)]
2 print(l)
3
4 l = (i*2 for i in range(10))
5 print(l)
6
7 运行结果:
8 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
9 <generator object <genexpr> at 0x000001BA440C35E8>
第二种方式:yield方式
斐波那契数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到:1,1,2,3,5,8,13,21,34...
1 def fib(max):
2 n,a,b = 0,0,1
3 while n<max:
4 print(b)
5 a,b = b,a+b
6 n = n+1
7 return 'done'
8
9 fib(5)
10
11 运行结果:
12 1
13 1
14 2
15 3
16 5
解析:a,b = b,a+b
相当于如下所示:
1 a,b = 0,1
2 t = (b,a+b) #t是一个tuple
3 a = t[0]
4 b = t[1]
5 print(a,b)
6
7 运行结果:
8 1,1
把上面案例中的print(b)改成yield b就变成了一个生成器。
1 def fib(max):
2 n,a,b = 0,0,1
3 while n<max:
4 yield b
5 a,b = b,a+b
6 n = n+1
7 return 'done'
8
9 fib(5) #此时这里就没有输出内容了,因为已经变成了一个生成器,可以通过__next__()取值
10 print(fib(5))
11
12 运行结果:
13 <generator object fib at 0x00000257090E35E8>
取值:
1 def fib(max):
2 n,a,b = 0,0,1
3 while n<max:
4 yield b
5 a,b = b,a+b
6 n = n+1
7 return 'done' #异常的时候打印的消息
8
9 t=fib(5)
10 print(fib(5))
11 for i in t: #使用for循环不会报StopIteration异常
12 print(i)
使用__next__()超过范围会报错StopIteration,如下
1 def fib(max):
2 n,a,b = 0,0,1
3 while n<max:
4 yield b
5 a,b = b,a+b
6 n = n+1
7 return 'done'
8
9 t=fib(5)
10 print(t.__next__())
11 print(t.__next__())
12 print(t.__next__())
13 print(t.__next__())
14 print(t.__next__())
15 print(t.__next__())
16
17 运行结果:
18 1
19 1
20 2
21 3
22 5
23 Traceback (most recent call last):
24 File "D:/Python/Project/python3.5/test.py", line 15, in <module>
25 print(t.__next__())
26 StopIteration: done
异常捕获并观察return后面的值:
1 def fib(max):
2 n,a,b = 0,0,1
3 while n<max:
4 yield b
5 a,b = b,a+b
6 n = n+1
7 return 'done'
8
9 t=fib(5)
10 while True:
11 try:
12 x = t.__next__()
13 print(x)
14 except StopIteration as e:
15 print('异常信息:',e.value)
16 break
17
18 运行结果:
19 1
20 1
21 2
22 3
23 5
24 异常信息: done
案例:使用yield在单线程的情况下实现并行运算的效果
首先搞清楚__next__()和send()方法的区别:
1 def gen(name):
2 print("%s在排队!"%name)
3 while True:
4 a = yield
5 print("[%s]来了,[%s]上车了!"%(a,name))
6
7 t = gen("Tom")
8 t.__next__()
9 t.__next__()
10 #next运行生成器,并不给yield赋值
11 t.send("公交车")
12 #send运行生成器并给yield赋值
13
14 运行结果:
15 Tom在排队!
16 [None]来了,[Tom]上车了!
17 [公交车]来了,[Tom]上车了!
案例开始:
import time
def consumer(name):
print("%s准备吃包子啦!"%name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!"%(baozi,name))
def producer(name):
c = consumer('A')
c.__next__()
print("开始做包子啦!")
for i in range(3):
time.sleep(1)
print("%s做了2个包子!"%name)
c.send(i)
producer('Andy')
'''
运行结果:
A准备吃包子啦!
开始做包子啦!
Andy做了2个包子!
包子[0]来了,被[A]吃了!
Andy做了2个包子!
包子[1]来了,被[A]吃了!
Andy做了2个包子!
包子[2]来了,被[A]吃了!
'''
生成器总结:
生成器只有在调用时才会生成相应的数据。
生成器只记录当前位置,只能向后取数据(__next__()),不能向前取数据。
创建一个generator后,基本上不会用__next__()调用,而是通过for循环来进行迭代,并且不需要关心StopIteration的错误。
2.迭代器
可以直接作用于for循环的数据类型如下:
- 集合数据类型:list、tuple、dict、set、str等
- generator:包括生成器和带yield的generator函数
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象。
1 from collections import Iterable
2
3
4 print(isinstance([],Iterable))
5 print(isinstance((),Iterable))
生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
1 from collections import Iterator
2
3
4 print(isinstance([],Iterator))
5 print(isinstance("",Iterator))
6 print(isinstance((i for i in range(10)),Iterator))
7
8 运行结果:
9 False
10 False
11 True
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
1 from collections import Iterator
2
3
4 print(isinstance(iter([]),Iterator))
5 print(isinstance(iter(""),Iterator))
6 print(isinstance((i for i in range(10)),Iterator))
7
8 运行结果:
9 True
10 True
11 True