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返回

posted @ 2018-04-23 16:43  woz333333  阅读(226)  评论(0编辑  收藏  举报