迭代器和生成器的用法

首先在了解解析式之前,我们先来看一个列子:一个列表,元素是0-9,列表中的每个值自增1,该如何实现:

方法一:遍历列表,对其元素进行加1操作后放到一个新的列表中

1 lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 
3 for index, i in enumerate(lst):
4     lst[index] += 1
5 print(lst)

方法二:通过map函数来实现

a = map(lambda x:x+1, lst)
print(a)
for x in a:
    print(x)

方法三:通过列表解析式,一行搞定

1 lst2 = [x+1 for x in lst]
2 print(lst2)

   方法三就是列表解析式的写法,返回一个新的列表。

 

  那么什么是生成器呢?通过列表解析式我们可以发现,它会直接创建一个新的列表,这样不好的地方就是占用内存,我们知道内存是有限的,如果列表中的元素有几百万,而有时候就仅仅需要个别的数据,那么就会大大浪费内存的空间。

  所以,我们是否可以想一种办法来解决这个问题呢?在Python中,生成器就很好的解决了这个问题。我们可以假设列表中的元素能否通过某种算法来推算出来呢?在需要某个数据的时候通过计算来得到这个数据,这样就不会直接生成一个列表来存储许多无用的数据了。在python中,这种一边循环一边计算的机制,就是生成器。

  生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,使用next()函数和send()函数可以恢复生成器继续下次计算。生成器不同于一般的函数会一次性返回所有数据,生成器一次只能产生一个数据,这样就不会占用大量的内存空间。

  那么如何去创建一个生成器,这种方式比较简单,就是将列表解析式的[]换成(),就可以创建一个生成器(generator):

1 generator_demo = (x for x in range(6))
2 print(generator_demo)
3 
4 运行结果:
5 <generator object <genexpr> at 0x0000000000C3E360>

  从结果可以看出,生成器返回的是一个generator对象。而列表解析式返回一个列表。那么如何去将生成器中的元素一个一个打印出来呢?这就需要next()函数来进行操作:

 1 generator_demo = (x for x in range(3))
 2 
 3 print(next(generator_demo))
 4 print(next(generator_demo))
 5 print(next(generator_demo))
 6 print(next(generator_demo))
 7 
 8 运行结果:
 9 0
10 1
11 2
12 Traceback (most recent call last):
13   File "C:/Users/wj/PycharmProjects/MxOnline/test.py", line 10, in <module>
14     print(next(generator_demo))
15 StopIteration

  可以看出,生成器保存的是算法,通过next可以一次一次计算它的元素,直到最后一个元素的时候继续next会抛出StopIteration的错误。在生产环境中,是基本不会用next这个方法的。因为生成器也是可迭代对象,可以通过for循环去迭代它的值:

1 generator_demo = (x for x in range(3))
2 
3 for x in generator_demo:
4     print(x)

  通过这种方法来迭代它的值就不会抛出StopIteration的错误了。

  这种创建生成器的写法固然简单,但是如果是一个算法逻辑比较复杂的时候,就不适合通过这种简单的写法来创建生成器了,我们可以通过函数的形式来创建生成器,例如著名的斐波那契数列:

1 def fib(max):
2     n, a, b = 0, 0, 1
3     while n < max:
4         a, b = b, a+b
5         print(a)
6         n += 1
7 
8     return 'done'

  可以看出,fib函数实际上就是定义了斐波那契数列的运算规则,从第一个元素开始,去推算出后面的元素,这种逻辑就非常想生成器。那么我们就可以将这个函数去定义成一个生成器函数,通过yield关键字来定义:

 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 += 1
 7 
 8 print(fib(10))
 9 
10 运行结果:
11 <generator object fib at 0x00000000011BE360>

  从运行结果可以看出,返回了一个generator对象,这就说明将这个函数定义成了一个生成器函数。这里说一下普通函数和生成器函数的执行流程,普通函数是顺序执行的,遇到return关键字就返回函数的值退出函数。而生成器函数是每次调用next的时候执行,遇到yield关键字返回第一个元素,然后将函数挂起,等待下一次唤醒继续计算后面的值,也就是取多少用多少,不会去占用内存空间。

  这个生成器函数也是可迭代对象,所以可以通过for循环去迭代他的值,而不用去通过next去取值。

  我们还可以通过yield来实现在单线程模式下实现并发运算的效果:

 1 import time
 2 
 3 def consumer(name):
 4     print('{}老师说:准备上课了'.format(name))
 5     while True:
 6         lesson = yield
 7         print('开始{}了,{}老手来讲课了!'.format(lesson, name))
 8 
 9 
10 def producer():
11     name1 = consumer('张老手')
12     name2 = consumer('王老手')
13     name1.__next__()
14     name2.__next__()
15 
16     for x in range(4):
17         time.sleep(1)
18         print('到了两名同学')
19         name1.send(x)
20         name2.send(x)

  从这个例子的运行结果可以看出,next和send的区别,next是唤醒函数,返回一次值,send是唤醒函数,并向生成器内部yield传递一个值,并改变yield的返回值。

 

  那么什么是迭代器呢?迭代就是循环,是指在正确的范围内返回期待的数据以及在超出范围以后抛出StopIteration的错误停止迭代。

  可以直接作用于for循环的数据类型有,字符串、列表、元组、字典、集合等,还有生成器也可以进行迭代,这些直接作用于for循环的对象都可以叫做可迭代对象Iterable。

  一个实现了iter方法的对象是可迭代的,一个实现了next方法并且是可迭代的对象是迭代器。

posted @ 2019-09-21 13:59  Sweltering  阅读(477)  评论(0编辑  收藏  举报