• Python生成器&迭代器


    1.生成器

    列表生成式:

    1 l = [i*2 for i in range(10)]
    2 print(l)

     

      通过列表生成式,可以直接创建一个列表,但是收到内存限制,列表容量是有限的。如果创建一个包含100w元素的列表,而我们只需要访问前面几个元素,那么就太占用空间了。如果列表元素可以按照某种算法推算出来,我们是否可以在循环过程中不断推算出后续元素呢,这样就不必创建完整的list,从而节省大量的空间,这样一边循环一边计算的机制称为生成器:generator。

    1.1 创建生成器

    第一种方式:把列表生成式的[]改成()就可以

    1 l = [i*2 for i in range(10)]
    2 print(l)
    3 
    4 l = (i*2 for i in range(10))
    5 print(l)
    6 
    7 运行结果:
    8 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    9 <generator object <genexpr> at 0x000001BA440C35E8>

     

    第二种方式:yield方式

      斐波那契数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到:1,1,2,3,5,8,13,21,34...

     1 def fib(max):
     2     n,a,b = 0,0,1
     3     while n<max:
     4         print(b)
     5         a,b = b,a+b
     6         n = n+1
     7     return 'done'
     8 
     9 fib(5)
    10 
    11 运行结果:
    12 1
    13 1
    14 2
    15 3
    16 5

    解析:a,b = b,a+b

    相当于如下所示:

    1 a,b = 0,1
    2 t = (b,a+b)  #t是一个tuple
    3 a = t[0]
    4 b = t[1]
    5 print(a,b)
    6 
    7 运行结果:
    8 1,1

      把上面案例中的print(b)改成yield b就变成了一个生成器。

     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 = n+1
     7     return 'done'
     8 
     9 fib(5) #此时这里就没有输出内容了,因为已经变成了一个生成器,可以通过__next__()取值
    10 print(fib(5))
    11 
    12 运行结果:
    13 <generator object fib at 0x00000257090E35E8>

    取值:

     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 = n+1
     7     return 'done'  #异常的时候打印的消息
     8 
     9 t=fib(5)
    10 print(fib(5))
    11 for i in t:  #使用for循环不会报StopIteration异常
    12     print(i)

    使用__next__()超过范围会报错StopIteration,如下

     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 = n+1
     7     return 'done'
     8 
     9 t=fib(5)
    10 print(t.__next__())
    11 print(t.__next__())
    12 print(t.__next__())
    13 print(t.__next__())
    14 print(t.__next__())
    15 print(t.__next__())
    16 
    17 运行结果:
    18 1
    19 1
    20 2
    21 3
    22 5
    23 Traceback (most recent call last):
    24   File "D:/Python/Project/python3.5/test.py", line 15, in <module>
    25     print(t.__next__())
    26 StopIteration: done

    异常捕获并观察return后面的值:

     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 = n+1
     7     return 'done'
     8 
     9 t=fib(5)
    10 while True:
    11     try:
    12         x = t.__next__()
    13         print(x)
    14     except StopIteration as e:
    15         print('异常信息:',e.value)
    16         break
    17 
    18 运行结果:
    19 1
    20 1
    21 2
    22 3
    23 5
    24 异常信息: done

     

    案例:使用yield在单线程的情况下实现并行运算的效果

    首先搞清楚__next__()和send()方法的区别:

     1 def gen(name):
     2     print("%s在排队!"%name)
     3     while True:
     4         a = yield
     5         print("[%s]来了,[%s]上车了!"%(a,name))
     6 
     7 t = gen("Tom")
     8 t.__next__()
     9 t.__next__()
    10 #next运行生成器,并不给yield赋值
    11 t.send("公交车")
    12 #send运行生成器并给yield赋值
    13 
    14 运行结果:
    15 Tom在排队!
    16 [None]来了,[Tom]上车了!
    17 [公交车]来了,[Tom]上车了!

    案例开始:

    import time
    
    def consumer(name):
        print("%s准备吃包子啦!"%name)
        while True:
            baozi = yield
            print("包子[%s]来了,被[%s]吃了!"%(baozi,name))
    
    def producer(name):
        c = consumer('A')
        c.__next__()
        print("开始做包子啦!")
        for i in range(3):
            time.sleep(1)
            print("%s做了2个包子!"%name)
            c.send(i)
    
    producer('Andy')
    
    
    '''
    运行结果:
    A准备吃包子啦!
    开始做包子啦!
    Andy做了2个包子!
    包子[0]来了,被[A]吃了!
    Andy做了2个包子!
    包子[1]来了,被[A]吃了!
    Andy做了2个包子!
    包子[2]来了,被[A]吃了!
    '''
    

      

    生成器总结:

    生成器只有在调用时才会生成相应的数据。
    生成器只记录当前位置,只能向后取数据(__next__()),不能向前取数据。
    创建一个generator后,基本上不会用__next__()调用,而是通过for循环来进行迭代,并且不需要关心StopIteration的错误。

     

     2.迭代器

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

    • 集合数据类型:list、tuple、dict、set、str等
    • generator:包括生成器和带yield的generator函数

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

    1 from collections import Iterable
    2 
    3 
    4 print(isinstance([],Iterable))
    5 print(isinstance((),Iterable))

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

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

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

     1 from collections import Iterator
     2 
     3 
     4 print(isinstance([],Iterator))
     5 print(isinstance("",Iterator))
     6 print(isinstance((i for i in range(10)),Iterator))
     7 
     8 运行结果:
     9 False
    10 False
    11 True

      

      生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

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

     1 from collections import Iterator
     2 
     3 
     4 print(isinstance(iter([]),Iterator))
     5 print(isinstance(iter(""),Iterator))
     6 print(isinstance((i for i in range(10)),Iterator))
     7 
     8 运行结果:
     9 True
    10 True
    11 True
  • 相关阅读:
    oracle中函数和存储过程的区别和联系
    oracle系统函数(日期函数)
    触发器
    初次使用集合
    框架结构中同时改变多个框架内容,并显示两个页面
    借贷记账法的原理
    IIS7对json支持
    SqlHelper和数据访问层
    在easyui datagrid中formatter数据后使用linkbutton
    jQuery 添加/移除CSS类
  • 原文地址:https://www.cnblogs.com/jmwm/p/10512284.html
Copyright © 2020-2023  润新知