• 列表生成式,生成器和迭代器


    1.列表生成式

    语法:[条件表达式  for i in iterable]

    a=list(range(10))
    b=[i+1 for i in a]   #这种形式就是列表生成式
    print(b)

    用列表生成式可以简化代码,等价于下面的几种方法:

     1 #方法一
     2 a=list(range(10))
     3 b=[]
     4 for i in a:
     5     b.append(i+1)
     6 
     7 print(b)
     8 
     9 #方法二
    10 a=list(range(10))
    11 for index,i in enumerate(a):
    12     a[index]+=1
    13 print(a)
    14 
    15 #方法三
    16 a=list(range(10))
    17 a=map(lambda a:a+1,a)  #返回的是一个内存地址,想要调用需要用for循环
    18 for i in a:
    19     print(i)

    2.生成器

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

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

    2.1 创建generator:

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

    1 l=[x*x for x in range(10)]
    2 print(l)
    3 g=(x*x for x in range(10))
    4 print(g)

    返回:

     生成器g返回的是函数的内存地址,想要打印出g里面的元素,可以使用

    for i in g:
        print(i)  

    返回

     注意:

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

      2.列表可以进行切片和索引,生成器g没有办法进行切片和索引。生成器只能在调用的时候才会返回相应的数据。

      3.打印生成器g的数据的方式,只有一个一个的取:一种是用for循环逐次打印,一种是用next()函数获得generator的下一个返回值。

      生成器只记住当前的位置,既不知道之前的,也不知道之后的,只能一个一个地往后下一个取。

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

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

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

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

    第二种方法:用函数做生成器,yield

     实例:斐波拉契Fibonaccl数列:除第一个和第二个数外,任意一个数都可由前两个数相加得到:

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

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

    1 def fibo(max):
    2     n,a,b=0,0,1
    3     while n<max:
    4         print(b)
    5         a,b=b,a+b  #等价于t=(a,a+b),a=t[0],b=t[1] 但不必显式写出临时变量t就可以赋值。
    6         n+=1
    7     return 'done'
    8 
    9 fibo(10)

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

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

     1 def fibo(max):
     2     n,a,b=0,0,1
     3     while n<max:
     4         yield b
     5         a,b=b,a+b  #等价于t=(a,a+b),a=t[0],b=t[1] 但不必显式写出临时变量t就可以赋值。
     6         n+=1
     7     return 'done'
     8 
     9 
    10 for i in fibo(10):
    11     print(i)

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

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

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

     1 def fibo(max):
     2     n,a,b=0,0,1
     3     while n<max:
     4         yield b
     5         a,b=b,a+b  #等价于t=(a,a+b),a=t[0],b=t[1] 但不必显式写出临时变量t就可以赋值。
     6         n+=1
     7     return 'done'  #异常时打印的消息
     8 
     9 
    10 data = fibo(10)
    11 print(data)
    12 
    13 print(data.__next__())
    14 print(data.__next__())
    15 print("干点别的事")
    16 print(data.__next__())
    17 print(data.__next__())
    18 print(data.__next__())
    19 print(data.__next__())
    20 print(data.__next__())
    21 print(data.__next__())
    22 print(data.__next__())
    23 print(data.__next__())
    24 print(data.__next__())
    25 print(data.__next__())

    返回:

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

    同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代。但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue

     1 def fibo(max):
     2     n,a,b=0,0,1
     3     while n<max:
     4         yield b   #想要返回什么,就在哪里加yield。
     5         a,b=b,a+b  #等价于t=(a,a+b),a=t[0],b=t[1] 但不必显式写出临时变量t就可以赋值。
     6         n+=1
     7     return 'done'
     8 
     9 f=fibo(10)
    10 while True:
    11     try:
    12         x=next(f)   #debugger中,x=next(f)就是调用f,到yield b中止,下次调用函数,则继续运行a,b=a,a+b;n+=1
    13         print('fibo:',x)
    14     except StopIteration as e:
    15         print('Generator return value:',e.value)
    16         break

    2.2 通过yield在单线程情况下实现并发运算的效果

     1 #典型的生产者-消费者模型
     2 import time
     3 def consumer(name):
     4     print('%s 准备吃包子啦!'%name)
     5     while True:
     6         baozi=yield   #通过下面的send给yield传值,baozi=c.send('object')
     7 
     8         print('包子[%s]来了,被[%s]吃了!'%(baozi,name))
     9 
    10 
    11 def producer(name):
    12     c0=consumer(name)
    13     c1=consumer('Zzz')
    14     c0.__next__()  #只有调用__next__()才能从开始调用consumer()函数
    15     c1.__next__()
    16     print('厨师开始做包子')
    17     for i in range(10):
    18         time.sleep(1)
    19         print('做了2个包子!')
    20         c0.send(i)
    21         c1.send(i)
    22 
    23 
    24 producer('david')

    返回:

    3.迭代器

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

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

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

    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

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

     

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

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

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

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

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

     

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

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

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

    小结

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

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

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

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

    1
    2
    for in [12345]:
        pass

    实际上完全等价于:

    复制代码
    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
    复制代码
  • 相关阅读:
    FZU 2098 刻苦的小芳(卡特兰数,动态规划)
    卡特兰数总结
    FZU 1064 教授的测试(卡特兰数,递归)
    HDU 4745 Two Rabbits(区间DP,最长非连续回文子串)
    Java 第十一届 蓝桥杯 省模拟赛 正整数的摆动序列
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 凯撒密码加密
    Java 第十一届 蓝桥杯 省模拟赛 凯撒密码加密
  • 原文地址:https://www.cnblogs.com/zoe233/p/7079864.html
Copyright © 2020-2023  润新知