知识内容:
1.列表生成式
2.生成器介绍
3.生成器函数
一、列表生成式
1.需求: 列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表中的每个值都加1,你如何实现
实现上述需求其实不难,有如下两种方法
(1)普通青年版
1 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 >>> for index, i in enumerate(a): 3 ... a[index] += 1 4 ... 5 >>> print(a) 6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10
(2)文艺青年版
1 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 >>> a = map(lambda x:x+1, a) 3 >>> a 4 <map object at 0x04CC38B0> 5 >>> for i in a: 6 ... print(i) 7 ... 8 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16 9 17 10
其实还有第3种方法,如下:
(3)装逼青年版
1 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 2 >>> a = [i+1 for i in a] 3 >>> a 4 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这样的写法就叫列表生成式
2.列表生成式语法
(1)列表生成式使用非常简洁的方式来快速生成满足特定需求的列表,代码具有非常强的可读性,例如:
1 a = [x*x for x in range(10)]
上面的代码等价于:
1 a = []
2 for x in range(10):
3 a.append(x*x)
1 # # 以下3段代码等价: 2 # f = [' banana ', ' loganberry ', ' passion fruit '] 3 # a = [w.strip() for w in f] 4 # print(a) 5 # 6 # f = [' banana ', ' loganberry ', ' passion fruit '] 7 # for i, v in enumerate(f): 8 # f[i] = v.strip() 9 # print(a) 10 # 11 # f = [' banana ', ' loganberry ', ' passion fruit '] 12 # f = list(map(str.strip, f)) 13 # print(a)
(2)循环与列表生成式
1 # ======一层循环====== 2 l = [i*i for i in range(1,10)] 3 print(l) 4 # 上面的列表推倒式就相当于下面的 5 l = [] 6 for i in range(1,10): 7 l.append(i*i) 8 print(l) 9 l = [] 10 11 12 # ======多层循环======== 13 # 1.列表推倒式 14 l = [i*j for i in range(1,10) for j in range(1,10)] 15 print(l) 16 # 2.循环 17 l = [] 18 for i in range(1,10): 19 for j in range(1,10): 20 s = i*j 21 l.append(s) 22 print(l)
二、生成器介绍
1.生成器定义
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都浪费了。所以如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器的好处,就是一下子不会在内存中生成太多的数据
2.生成器的本质:就是一个迭代器
3.使用生成器
(1)创建生成器
创建生成器有两种方法: 生成器表达式和生成器函数,生成器表达式如下所示,生成器函数见后面详解
把列表生成式中的[]改成()就可以创建一个generator,这样来创建生成器的方法叫生成器表达式
1 >>> a = (i for i in range(10)) 2 >>> a 3 <generator object <genexpr> at 0x03004F00>
(2)next函数
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
1 >>> next(a) 2 0 3 >>> next(a) 4 1 5 >>> next(a) 6 2 7 >>> next(a) 8 3 9 >>> next(a) 10 4 11 >>> next(a) 12 5 13 >>> 14 >>> next(a) 15 6 16 >>> next(a) 17 7 18 >>> 19 >>> next(a) 20 8 21 >>> next(a) 22 9 23 >>> next(a) 24 Traceback (most recent call last): 25 File "<stdin>", line 1, in <module> 26 StopIteration
注: generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误! 因为生成器还是可迭代对象,所以遍历生成器我们依然使用for循环,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误
(3)遍历生成器
for循环遍历:
1 >>> a = (i for i in range(10)) 2 >>> for i in a: 3 ... print(i, end=" ") 4 ... 5 0 1 2 3 4 5 6 7 8 9
while循环遍历(结合异常处理):
1 s = (i for i in range(10)) 2 3 while True: 4 try: 5 x = next(s) 6 print(x) 7 except StopIteration as e: 8 print('Generator return value:', e.value) 9 break
三、生成器函数
1.生成器函数实例
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
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 "OK"
仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
1 def fib(max): 2 n,a,b = 0,0,1 3 4 while n < max: 5 #print(b) 6 yield b 7 a,b = b,a+b 8 n += 1 9 return "OK"
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行
2.生成器函数定义
生成器函数是常规定义函数,但是,使用yield语句而不是return语句返回结果,yield语句一次返回一个结果。第一次调用生成器函数是生成了一个生成器,然后在每次调用next()
的时候执行生成器函数,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行
1 def fib(max): 2 n, a, b = 0, 0, 1 3 while n < max: 4 # print(b) 5 print("before yield") 6 yield b # 把函数的执行冻结在这一步,并把b的值返回给外面的next() 7 print(b) 8 a, b = b, a + b 9 n += 1 10 11 12 f = fib(15) # turn function into a generator 13 next(f) 14 next(f) 15 next(f) 16 next(f)
注: 只要函数中有yield,解释器就就会将其看作一个生成器!
3.关于range函数
在python2中range返回的是list,xrange返回的是生成器对象;而python3中只有range,没有xrange,并且在python3中range返回的是生成器对象
用生成器实现range函数:
1 def range_wyb(n): 2 count = 0 3 while count < n: 4 # print(count) 5 yield count 6 count += 1 7 8 9 # range_wyb(12) 10 x = range_wyb(12) # 创建一个生成器对象 11 print(x) 12 # next(x) 13 # next(x) 14 # next(x) 15 for i in x: # 遍历生成器 16 print(i, end=" ")
4.yield、return、next的区别
- return: 返回并终止函数
- yield: 返回数据并冻结当前的执行过程
- next: 唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield
另外,函数有了yield之后:
- 函数名加上()就得到了一个生成器
- return在生成器里,代表生成器的中止,直接报错
5.总结
关于生成器:
- 只有在调用时才会生成值
- 只记录当前位置
- 可以用生成器表达式或生成器函数生成
- 只有一个__next__方法,next()
- 生成器也可以使用for循环
关于生成器函数:
- 语法上和函数类似
- 自动实现迭代器协议
- 状态挂起:通过yield实现状态挂起
生成器的优缺点:
优点:
延迟计算,一次返回一个结果,它不会一次生成所有的结果,这对于大数据处理非常有用
生成器还能有效提高代码可读性
6.单线程实现并行(协程)
1 # 通过生成器实现协程并行运算 (生成者消费者模型) 2 import time 3 4 5 def consumer(name): 6 print("%s 准备吃包子啦!" % name) 7 while True: 8 baozi = yield # 保存当前状态并返回 9 print("包子[%s]来了,被[%s]吃了!" % (baozi, name)) 10 11 12 c = consumer("woz") 13 c.__next__() 14 b1 = "猪肉馅" 15 c.send(b1) # send->给yield传值 16 17 18 def producer(name): 19 c = consumer('A') 20 c2 = consumer('B') 21 c.__next__() 22 c2.__next__() 23 print("%s开始准备做包子啦!" % name) 24 for i in range(10): 25 time.sleep(1) 26 print("做了2个包子!") 27 c.send(i) 28 c2.send(i) 29 30 31 producer("Wyb")
注:消费函数调用后要next的原因:
消费函数调用是生成一个生成器,并未执行里面的代码,只有next之后才能执行里面的代码然后到yield返回