• python入门第十七天_生成器 迭代器


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

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

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

    列表生成式:

    1 s=[x*2 for x in range(50)] #这是列表生成式 
    2 print(s)

    改成生成器:

    1 s=(x*2 for x in range(50)) #这是列表生成式 在Python中,这种一边循环一边计算的机制,称为生成器:generator。
    2 print(s)
    3 

    结果:

    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98]
    <generator object <genexpr> at 0x0000000001E0EE08>

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

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

    1 s=(x*2 for x in range(50)) #这是列表生成式 在Python中,这种一边循环一边计算的机制,称为生成器:generator。
    2 print(s)
    3 
    4 print(s.__next__())  #__next__ 是函数的私有方法。
    5 print(next(s))
    6 print(next(s))
    <generator object <genexpr> at 0x000000000069EEB8>
    0
    2
    4

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

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

    1 s=(x*2 for x in range(10)) #这是列表生成式 在Python中,这种一边循环一边计算的机制,称为生成器:generator。
    2 print(s)
    3 
    4 # print(s.__next__())  #__next__ 是函数的私有方法。
    5 # print(next(s))
    6 # print(next(s))
    7 for i in s:
    8     print(i)

    结果:

    <generator object <genexpr> at 0x000000000067EE08>
    0
    2
    4
    6
    8
    10
    12
    14
    16
    18

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

    generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

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

    1, 1, 2, 3, 5, 8, 13, 21, 34, ...

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

    #第十三天 递归函数
    def f(n):
        before=0
        after=1
        if n==1:
            return 0
        if n==2:
            return 1
        for i in range(0,n-2):
            before,after= after,after+before
        return after
    
    for i in range(1,9):
        print(f(i))

    递归函数:

    def f(n):
        if n==1:
            return 0
        if n==2:
            return 1
        sum=f(n-1)+f(n-2)
        return sum
     
    for i in range(1,9):
        print(f(i))
    

    生成一个斐波拉契数列:

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

    打印结果:

    1
    1
    2
    3
    5
    8
    done

    仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

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

     1 def fib(max):
     2     n,before,after=0,0,1
     3     while n<max:
     4 #         print(after)
     5         yield after
     6         before,after=after,before+after
     7         n+=1
     8     return 'done'
     9 
    10 print(fib(6))

    结果: <generator object fib at 0x000000000214EE08> 

    这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

    >>> f = fib(6)
    >>> f
    <generator object fib at 0x104feaaa0>

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

    我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

    同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

    1 >>> for n in fib(6):
    2 ...     print(n)
    3 ...
    4 1
    5 1
    6 2
    7 3
    8 5
    9 8

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

    >>> 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: done

     杨辉三角:

     1 '''
     2 杨辉三角定义如下:
     3 
     4           1
     5          / 
     6         1   1
     7        /  / 
     8       1   2   1
     9      /  /  / 
    10     1   3   3   1
    11    /  /  /  / 
    12   1   4   6   4   1
    13  /  /  /  /  / 
    14 1   5   10  10  5   1
    15 把每一行看做一个list,试写一个generator,不断输出下一行的list:
    16 
    17 '''
    18 
    19 def triangles():
    20     L=[0,1,0] 
    21     n=1   
    22     while n<10:
    23         
    24         L_print=[]
    25 #         yield ("打印第 %s,数据是%s"%(len(L)-2,L[1:-1]))
    26         
    27         print("打印第 %s,数据是%s"%(len(L)-2,L[1:-1]))
    28         for i in range(len(L)-1):
    29             L_print.append(L[i]+L[i+1])
    30 #         L=[0]+L_print+[0]
    31         L_print.insert(0,0)
    32         L_print.append(0)
    33         L=L_print
    34         n+=1
    35         
    36 
    37 p=triangles() 
    38 # print(next(p))
    39 # print(next(p))
    40 # print(next(p))
    41 # print(next(p))
    42 # print(next(p))
    43 # print(next(p))
    44 print(p)

    结果:

    打印第 1,数据是[1]
    打印第 2,数据是[1, 1]
    打印第 3,数据是[1, 2, 1]
    打印第 4,数据是[1, 3, 3, 1]
    打印第 5,数据是[1, 4, 6, 4, 1]
    打印第 6,数据是[1, 5, 10, 10, 5, 1]
    打印第 7,数据是[1, 6, 15, 20, 15, 6, 1]
    打印第 8,数据是[1, 7, 21, 35, 35, 21, 7, 1]
    打印第 9,数据是[1, 8, 28, 56, 70, 56, 28, 8, 1]
    None

    理解生成器:

     1 def foo():
     2     n=1
     3     while n<5:
     4         yield n
     5         n=n+1
     6     
     7 p=foo() 
     8 
     9 print(p)
    10 for i in range(5):
    11     print(next(p))

    结果:

    <generator object foo at 0x000000000213EEB8>
    1
    2
    3
    4
    Traceback (most recent call last):
      File "F:python从入门到放弃72生成器.py", line 18, in <module>
        print(next(p))
    StopIteration

    超出取值范围会报错 StopIteration

    def foo():
        print('OK')
        yield '1'
        print('OK2')
        yield '2'
        print('OK3')
        yield '3'
        print('OK4')
        yield '4'
        return 8
    
    # for i in foo():
    # 
    #     print(i)
    p=foo()
    
    a=next(p)
    b=next(p)
    print(next(p))
    d=next(p)
    
    
    
    print(a,b,d)

    结果:

    OK
    OK2
    OK3
    3
    OK4
    1 2 4

    迭代器

     

    我们已经知道,可以直接作用于for循环的数据类型有以下几种:

    一类是集合数据类型,如listtupledictsetstr等;

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

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

    >>> from collections import Iterable
    >>> isinstance([], Iterable)
    True
    >>> isinstance({}, Iterable)
    True
    >>> isinstance('abc', Iterable)
    True
    >>> isinstance((x for x in range(10)), Iterable)
    True
    >>> isinstance(100, Iterable)
    False

    而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

    可以使用isinstance()判断一个对象是否是Iterator对象:

    >>> from collections import Iterator
    >>> isinstance((x for x in range(10)), Iterator)
    True
    >>> isinstance([], Iterator)
    False
    >>> isinstance({}, Iterator)
    False
    >>> isinstance('abc', Iterator)
    False

    生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

    listdictstrIterable变成Iterator可以使用iter()函数:

    >>> isinstance(iter([]), Iterator)
    True
    >>> isinstance(iter('abc'), Iterator)
    True

    你可能会问,为什么listdictstr等数据类型不是Iterator

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

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

    小结

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

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

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

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

    for x in [1, 2, 3, 4, 5]:
        pass

    实际上完全等价于:

     1 # 首先获得Iterator对象:
     2 it = iter([1, 2, 3, 4, 5])
     3 # 循环:
     4 while True:
     5     try:
     6         # 获得下一个值:
     7         x = next(it)
     8     except StopIteration:
     9         # 遇到StopIteration就退出循环
    10         break

     生成器的调用 next()   和 send()

     b.send(*args)调用的同时,传入一个参数:

     1 def foo():
     2     print('OK')
     3 
     4     count=yield 1
     5     print(count)
     6 
     7     print('OK2')
     8     ciui=yield 2
     9 b=foo()
    10 # print(next(b))
    11 b.send(None) # 这句代码等同于   next(b)
    12 '''
    13 第一次send 前 ,如果没有next,传一个send(None)
    14 '''
    15 
    16 
    17 b.send(8)

    结果:

    "D:Program Files (x86)python36python.exe" F:/python从入门到放弃/7.2/生成器2.py
    OK
    8
    OK2

     利用yield 编写一个伪并发程序:

     1 import time
     2 
     3 def consumer(name):
     4     print('%s 准备吃饭月饼了!'%name)
     5     while True:
     6         baozi=yield
     7 
     8         print('编号为{%s}}的包子来了,被{%s}吃了'%(baozi,name))
     9 
    10 def producer():
    11     c=consumer('李雷')
    12     c2=consumer('韩梅梅')
    13     c.__next__()
    14     c2.__next__()
    15     print('开始准备做包子啦')
    16     for i in range(1,10):
    17         time.sleep(1)
    18         print('做了两个包子,你们一人一个呀')
    19         c.send(i)
    20         c2.send(i)
    21 
    22 producer()

    结果:

    "D:Program Files (x86)python36python.exe" F:/python从入门到放弃/7.2/生成器2.py
    李雷 准备吃饭月饼了!
    韩梅梅 准备吃饭月饼了!
    开始准备做包子啦
    做了两个包子,你们一人一个呀
    编号为{1}}的包子来了,被{李雷}吃了
    编号为{1}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{2}}的包子来了,被{李雷}吃了
    编号为{2}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{3}}的包子来了,被{李雷}吃了
    编号为{3}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{4}}的包子来了,被{李雷}吃了
    编号为{4}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{5}}的包子来了,被{李雷}吃了
    编号为{5}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{6}}的包子来了,被{李雷}吃了
    编号为{6}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{7}}的包子来了,被{李雷}吃了
    编号为{7}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{8}}的包子来了,被{李雷}吃了
    编号为{8}}的包子来了,被{韩梅梅}吃了
    做了两个包子,你们一人一个呀
    编号为{9}}的包子来了,被{李雷}吃了
    编号为{9}}的包子来了,被{韩梅梅}吃了
    
    Process finished with exit code 0
  • 相关阅读:
    谬论之程序猿的眼光看世界
    phpStudy-坑爹的数据库管理器-phpMyAdmin的默认用户名和密码
    解决Delphi 2010启动时出现cannot create xxxxEditorLineEnds.ttr问题
    数据库中增加操作insert into的用法和查询select的用法
    TRichEdit怎样新增的内容到最后一行?
    BCB将RichEdit光标移到最后一行
    怎么把焦点放在RichEdit的最后一行
    Panel自动变颜色
    怎么判断pagecontrol下的TabSheet是否打开还是关闭求答案
    用Setup Factory7.0怎样打包delphi的BDE?
  • 原文地址:https://www.cnblogs.com/Mengchangxin/p/9252859.html
Copyright © 2020-2023  润新知