zoukankan      html  css  js  c++  java
  • 07生成器与迭代器

    生成器

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

    In [3]:
    L = [x + 1 for x in range(5)]
    L
    
    Out[3]:
    [1, 2, 3, 4, 5]
    In [4]:
    g = (x + 1 for x in range(5))
    g
    
    Out[4]:
    <generator object <genexpr> at 0x000000000BC68620>
     

    创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

    如果要一个一个打印出来,可以通过next()函数(or next())获得generator的下一个返回值:

    In [5]:
    next(g)
    
    Out[5]:
    1
    In [6]:
    next(g)
    
    Out[6]:
    2
    In [7]:
    next(g)
    
    Out[7]:
    3
    In [8]:
    next(g)
    
    Out[8]:
    4
    In [9]:
    next(g)
    
    Out[9]:
    5
    In [10]:
    next(g)
    
     
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-10-e734f8aca5ac> in <module>
    ----> 1next(g)
    
    StopIteration: 
     

    generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

    上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

    In [11]:
    g = (x * x for x in range(10))
    for n in g:
        print(n, end="; ")
    
     
    0; 1; 4; 9; 16; 25; 36; 49; 64; 81; 
     

    所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

     

    通过使用yield关键字定义

     

    生成器对象是通过使用yield关键字定义的函数对象,因此,生成器也是一个函数。生成器用于生成一个值得序列,以便在迭代器中使用。

    In [12]:
    """
    第一是直接作为脚本执行,
    第二是import到其他的python脚本中被调用(模块重用)执行。
    因此if __name__ == '__main__': 的作用就是控制这两种情况执行代码的过程,
    在if __name__ == '__main__':下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执行,而import到其他脚本中是不会被执行的。
    """
    
    def myYield(n):
        while n > 0:
            print('开始生成。。。')
            yield n
            print('完成一次。。。')
            n -= 1
            
    if __name__ == '__main__':
        a = myYield(3)
        print('已经实例化生成器对象')
    #     a.__next__()
    #     print('第二次调用__next__()方法:')
    #     a.__next__()
    
     
    已经实例化生成器对象
    
     

    yield 语句是生成器中的关键语句,生成器在实例化时并不会被执行,而是等待调用其next()方法才开始运行。并且当程序运行完yield语句后就会“吼(hold)住”,即保持当前状态且停止运行,等待下一次遍历时才恢复运行。

    程序运行的结果中的空行后的输出“已经实例化生成器对象”之前,已经实例化了生成器对象,但生成器并没有运行(没有输出‘开始生成’)。当第一次手工调用next()方法之后,才输出‘开始生成’,标志着生成器已经运行,而在输出‘’第二次调用next()方法‘’之前并没有输出‘完成一次’,说明yield语句运行之后就立即停止了。而第二次调用next()方法之后,才输出‘完成一次’,说明生成器的回复运行是从yield语句之后开始运行的

    In [13]:
    a.__next__()
    
     
    开始生成。。。
    
    Out[13]:
    3
    In [14]:
    a.__next__()
    
     
    完成一次。。。
    开始生成。。。
    
    Out[14]:
    2
     

    著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

    斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

    In [15]:
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'
    
    In [16]:
    fib(5)
    
     
    1
    1
    2
    3
    5
    
    Out[16]:
    'done'
     

    上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

    In [17]:
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            #print(b)
            yield b
            a, b = b, a + b
            n += 1
        return 'well done'
    
     

    这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

    In [18]:
    f= fib(5)
    f
    
    Out[18]:
    <generator object fib at 0x000000000BC687D8>
    In [19]:
    f.__next__()
    
    Out[19]:
    1
    In [20]:
    f.__next__()
    
    Out[20]:
    1
    In [21]:
    f.__next__()
    
    Out[21]:
    2
     

    用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

    In [22]:
    g = fib(6)
    while True:
        try:
            x = next(g)
            print('g:', x)
        except StopIteration as e:
            print('Generator return value:', e.value)
            break
    
     
    g: 1
    g: 1
    g: 2
    g: 3
    g: 5
    g: 8
    Generator return value: well done
    
     

    生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,此外,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能。现在就可以动手重构你的代码了,但凡看到类似

     

    def something(): result= [] for ... in ...: result.append(x) return result

    都可以用生成器函数来替换:

    def iter_something(): result = [] for ... in ...: yield x

     

    杨辉三角

    期待输出:

    In [23]:
    def triangles():
        result = [1]
        while True:
            yield result
            result = [1] + [result[i] + result[i+1] for i in range(len(result)-1)] + [1]
    
    In [24]:
    n = 0
    results = []
    for t in triangles():
        print(t)
        results.append(t)
        n = n + 1
        if n == 10:
            break
    if results == [
        [1],
        [1, 1],
        [1, 2, 1],
        [1, 3, 3, 1],
        [1, 4, 6, 4, 1],
        [1, 5, 10, 10, 5, 1],
        [1, 6, 15, 20, 15, 6, 1],
        [1, 7, 21, 35, 35, 21, 7, 1],
        [1, 8, 28, 56, 70, 56, 28, 8, 1],
        [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
    ]:
        print('测试通过!')
    else:
        print('测试失败!')
    
     
    [1]
    [1, 1]
    [1, 2, 1]
    [1, 3, 3, 1]
    [1, 4, 6, 4, 1]
    [1, 5, 10, 10, 5, 1]
    [1, 6, 15, 20, 15, 6, 1]
    [1, 7, 21, 35, 35, 21, 7, 1]
    [1, 8, 28, 56, 70, 56, 28, 8, 1]
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
    测试通过!
    
     

    迭代器

     

    可以直接作用于for循环的数据类型有以下几种:

    一类是集合数据类型,如list,tuple,dict,set,str等 一类是generator ,包括生成器和带yeild的generator function

    这些可以 直接作用于for循环的对象统称为可迭代对象:Iterable 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

    In [25]:
    a = [i for i in range(10)]
    next(a)
    
     
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-25-8981550fe3e0> in <module>
          1 a = [i for i in range(10)]
    ----> 2next(a)
    
    TypeError: 'list' object is not an iterator
     

    list,dict,str虽然是Iterable,却不是Iterator。

    In [26]:
    from collections import Iterator
    from collections import Iterable
    print(isinstance(a,Iterator))
    print(isinstance(a,Iterable))
    print(isinstance({},Iterable))
    print(isinstance('abc',Iterable))
    
     
    False
    True
    True
    True
    
     

    生成器就是一个迭代器

    In [27]:
    a = (i for i in range(10))
    print(next(a))
    print(isinstance(a, Iterator))
    
     
    0
    True
    
     

    iter()函数 创建迭代器

     

    iter(iterable)#一个参数,要求参数为可迭代的类型

    把list、dict、str等Iterable变成Iterator可以使用iter()函数:

    In [28]:
    print(isinstance({},Iterator))
    print(isinstance('abc',Iterator))
    print(isinstance(iter({}),Iterator))
    print(isinstance(iter('abc'),Iterator))
    
     
    False
    False
    True
    True
    
     

    你可能会问,为什么list、dict、str等数据类型不是Iterator?

    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。 Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

     

    小结

     

    是可作用于for循环的对象都是Iterable类型;

    凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

    集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

    Python的for循环本质上就是通过不断调用next()函数实现的,例如:

    In [29]:
    for x in [1, 2, 3, 4, 5]:
        print(x,end=',')
    
     
    1,2,3,4,5,
    In [30]:
    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
            print(x,end=',')
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
    
     
    1,2,3,4,5,
  • 相关阅读:
    Hihocoder 1275 扫地机器人 计算几何
    CodeForces 771C Bear and Tree Jumps 树形DP
    CodeForces 778D Parquet Re-laying 构造
    CodeForces 785E Anton and Permutation 分块
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale 二分
    Hexo Next 接入 google AdSense 广告
    如何统计 Hexo 网站的访问地区和IP
    Design and Implementation of Global Path Planning System for Unmanned Surface Vehicle among Multiple Task Points
    通过ODBC接口访问人大金仓数据库
  • 原文地址:https://www.cnblogs.com/xinmomoyan/p/10890621.html
Copyright © 2011-2022 走看看