• 迭代器和生成器的用法


    首先在了解解析式之前,我们先来看一个列子:一个列表,元素是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方法并且是可迭代的对象是迭代器。

  • 相关阅读:
    [HEOI2013]Eden 的新背包问题
    [UOJ#77]A+B Problem
    [CodeForces]786B Legacy
    [LUOGU]P4098[HEOI2013]ALO
    [BZOJ3207]花神的嘲讽计划
    [LUOGU]P2633 Count on a tree
    【东莞市选2007】拦截导弹
    [JZOJ] 3462. 【NOIP2013模拟联考5】休息(rest)
    [BZOJ] 2705: [SDOI2012]Longge的问题
    [BZOJ] 1191: [HNOI2006]超级英雄Hero
  • 原文地址:https://www.cnblogs.com/Sweltering/p/11531325.html
Copyright © 2020-2023  润新知