zoukankan      html  css  js  c++  java
  • 生成器generator和迭代器Iterator

    一、列表生成式
           在学习生成器迭代器之前先了解一下什么是列表生成式,列表生成式是Python内置的非常简单却强大的可以用来创建list的生成式。什么意思?举个例子,如果想生成列表[0,1,2,3,4,5]可以使用list(range(6)),但是如果想要生成[,,,,,]即[0,1,4,9,16,25]怎么做?

    #方法一:循环
    >>> L = []
    >>> for x in range(6):
    ... L.append(x**2)
    ...
    >>> L
    [0, 1, 4, 9, 16, 25]
    #方法二:列表生成式
    >>> [x**2 for x in range(6)]
    [0, 1, 4, 9, 16, 25]
            观察方法一、方法二可以发现,使用方法二列表生成式更为简洁、明了,方法二就是列表生成式。再来几个例子加深理解:

    #计算正整数0-5之间偶数平方
    >>> [x**2 for x in range(6) if x%2==0]
    [0, 4, 16]

    #列表生成式使用两个变量
    >>> d = {'a':'1','b':'2','c':'3'}
    >>> [k+'='+v for k,v in d.items()]
    ['a=1', 'b=2', 'c=3']
    二、生成器
           通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

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

     1.如何创建生成器

            方法一:创建生成器最简单的方法就是把一个列表生成式的 [] 改为 () ,就创建了一个生成器: 

    #列表生成式
    >>> L = [x**2 for x in range(6)]
    >>> L
    [0, 1, 4, 9, 16, 25]
     
    #生成器
    >>> g = (x**2 for x in range(6))
    >>> g
    <generator object <genexpr> at 0x00000173FFBFA1A8>
     
    注:L与g的区别仅在于最外层的[]和(),L是一个列表,而g是一个生成器。

         方法二:要把普通函数变为生成器(generator),只需把 print(b) 改为 yield b 就可以了。 如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器(generator)。

     2.如何获得生成器的每一个元素?

           方法一:如果要一个一个打印出来,可以通过 next() 函数获得生成器的下一个返回值。生成器保存的是算法,每次调用next(),就计算g的下一个元素的值,直到计算到最后一个元素,没有更多元素时,抛出StopIteration错误。

    #通过next()获得生成器的每一个元素
    >>> g = (x**2 for x in range(6))
    >>> g
    <generator object <genexpr> at 0x00000173FFBFA1A8>
    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9
    >>> next(g)
    16
    >>> next(g)
    25
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

      方法二:使用for循环

    #通过for循环获得生成器的每一个值
    >>> g = (x**2 for x in range(6))
    >>> for i in g:
    ...     print(i)
    ...
    0
    1
    4
    9
    16
    25

    3.生成器实例

           练习一:著名的斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都可以由前两个数相加得到:1,1,2,3,5,8,13,21,34...用列表生成式写不出来,如何用函数实现?

    #方法一:
    >>> def fib(max):
    ...     n,a,b = 0,0,1
    ...     while n < max:
    ...             print(b)
    ...             a,b = b,a+b
    ...             n += 1
    ...     return 'done'
    ...
    >>> fib(6)
    1
    1
    2
    3
    5
    8
    'done'
     
    #注:a,b = b,a+b  相当于 t = (b,a+b)  a = t[0]  b = t[1]
    #方法二:生成器
    >>> def fib(max):
    ...     n,a,b = 0,0,1
    ...     while n < max:
    ...             yield b
    ...             a,b = b,a+b
    ...             n += 1
    ...     return 'done'
    ...
    >>> f = fib(6)
    >>> f
    <generator object fib at 0x00000173FFBFA1A8>
    >>> for i in f:
    ...     print(i)
    ...
    1
    1
    2
    3
    5
    8

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

    #生成器函数每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
     
    >>> def odd():
    ...     print('第一步')
    ...     yield 100
    ...     print('第二步')
    ...     yield 200
    ...     print('第三步')
    ...     yield 300
    ...
    >>> g = odd()
    >>> next(g)
    第一步
    100
    >>> next(g)
    第二步
    200
    >>> next(g)
    第三步
    300
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    >>>
     
    #odd()生成器函数,在执行过程中遇到yield就中断,下次继续执行。执行3次yield后,已经没有yield可以执行,所以第4次调用报错。

    生成器总结:

           a、在Python中可以简单地把列表生成式改为generator,也可以通过函数实现复杂逻辑的generator。生成器的工作原理是在for循环过程中不断计算出下一个元素,并在适当条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

           b、普通函数和生成器函数区别:普通函数调用直接返回结果,而生成器函数的“调用”实际返回一个生成器对象

    #普通函数
    >>> r = abs(-1)
    >>> r
    1
     
    #生成器函数
    >>> g = fib(6)
    >>> g
    <generator object fib at 0x00000173FFBFA1A8>

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

           一类是集合数据类型,如list、tuple、dict、set、str 等;

           一类是generator,包括生成器和带 yield 的generator function。

           这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象:

    #判断一个对象是否是可迭代对象Iterable
    >>> from collections import Iterable
    >>> isinstance([],Iterable)
    True
    >>> isinstance((),Iterable)
    True
    >>> isinstance({},Iterable)
    True
    >>> isinstance('aaa',Iterable)
    True
    >>> isinstance((x for x in range(5)),Iterable)
    True
    >>> isinstance(100,Iterable)
    False
    >>> isinstance(True,Iterable)
    False

    可以被 next() 函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:

    #判断一个对象是否是迭代器:Iterator
    >>> from collections import Iterator
    >>> isinstance([],Iterator)
    False
    >>> isinstance((),Iterator)
    False
    >>> isinstance({},Iterator)
    False
    >>> isinstance('aaa',Iterator)
    False
    >>> isinstance((x for x in range(5)),Iterator)
    True

    可以发现生成器都是迭代器(Iterator)对象,但list、tuple、dict、str虽然是可迭代对象(Iterable),却不是迭代器(Iterator)。那有没有什么方法可以使list、tuple、dict、str等可迭代对象(Iterable)变成迭代器(Iterator)?  iter() 函数可以做这件事!

    #iter()将list、tuple、dict、set、str等可迭代对象变为迭代器
    >>> isinstance(iter([]),Iterator)
    True
    >>> isinstance(iter(()),Iterator)
    True
    >>> isinstance(iter({}),Iterator)
    True
    >>> isinstance(iter('aaa'),Iterator)
    True

    为什么list、dict、str等数据类型不是Iterator?这是因为Python的迭代器(Iterator)对象表示的是一个数据流,迭代器(Iterator)对象可以被next() 函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

           迭代器(Iterator)甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

    迭代器总结:

           a、凡是可作用于 for 循环的对象都是可迭代(Iterable) 类型;

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

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

           d、Python的for循环本质上就是通过不断调用next()函数实现的。

    for x in [1, 2, 3, 4, 5]:
        pass
     
    实际上完全等价于:
     
    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break

    四、迭代器生成器关系


           1.对于可迭代对象,可以通过 iter() 函数获得迭代器对象,并且可以被next() 函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。

           2.生成器是一种特殊的迭代器,生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。return 与 yield 不同之处在于return返回后,函数会释放,而生成器不会。在直接调用next()方法或者用 for 语句进行下一次迭代时,生成器会从yield 下一句开始执行,直至遇到下一个 yield。

           3.迭代器可以多次迭代,而生成器只能迭代一次。

    #迭代器:可以多次迭代
    >>> g = [x*x for x in range(5)]
    >>> g
    [0, 1, 4, 9, 16]
    >>> for i in g:
    ...     print(i)
    ...
    0
    1
    4
    9
    16
    >>> g
    [0, 1, 4, 9, 16]
    >>> for i in g:
    ...     print(i)
    ...
    0
    1
    4
    9
    16
     
     
    #生成器:只能迭代一次
    >>> f = (x*x for x in range(5))
    >>> f
    <generator object <genexpr> at 0x0000022BA065F0F8>
    >>> for i in f:
    ...     print(i)
    ...
    0
    1
    4
    9
    16
    >>> f
    <generator object <genexpr> at 0x0000022BA065F0F8>
    >>> for i in f:
    ...     print(i)
    ...
    >>>
  • 相关阅读:
    牛客小白月赛21
    牛客小白月赛21
    CodeForces 1333-C Eugene and an array(子区间和为0、前缀和)
    页面大小、页表项、虚拟地址和物理地址之间的关系(转)
    001-Paint_FreePythonGames项目代码详解(每行都有注释!!!)
    第17讲~第19讲:函数:python的乐高积木
    第16讲:序列!序列!
    第15讲:字符串格式化
    练习23--字符串、字节和编码
    第14讲:字符串--各种奇葩内置方法
  • 原文地址:https://www.cnblogs.com/ceo-python/p/11599631.html
Copyright © 2011-2022 走看看