python生成器

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

回到顶部