python生成器
知识内容:
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返回
以上是 python生成器 的全部内容, 来源链接: utcz.com/z/387475.html