zoukankan      html  css  js  c++  java
  • 初学Python——列表生成式、生成器和迭代器

    一、列表生成式

    假如现在有这样一个需求:快速生成一个列表[1,2,3,4,5,6,7,8,9,10],该如何实现?

    在不知道列表生成式的情况下,可能会这样写:

    a=[1,2,3,4,5,6,7,8,9,10]

    如果要每个值+1呢?可能会这样:

    for index,i in enumerate(a):
        a[index] +=1
    print(a)

    不够方便,这里讲一个快速生成列表的方法:列表生成式。意思就是立即生成列表。

    生成一个1到10的列表:

    a = [i+1 for i in range(10)]
    print( a)
    # output:
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    生成一个2~20的偶数列表:

    a=[ i*2 for i in rang(1,11)]
    print(a)
    # output:
    [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

    它相当于:

    a=[]
    for i in range(1,11): #列表生成式
        a.append(i*2)
    print(a)

    生成的列表已经存在在内存中。

    二、生成器

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

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

    要创建一个生成器,只需要把列表生成式中的 [ ] 改成 ( ) 即可。

    b=[i*2 for i in rang(10)] # 列表生成式
    print(b)
    
    c=( i*2 for i in range(10) ) #生成器
    print(c)
    
    # output:
    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    <generator object <genexpr> at 0x000001D0089B45C8>

    输出c,得到的是数据类型说明和它的内存地址。

    生成器只是名义上生成一个列表,但实际上却没有占用那么大内存,生成器只有调用的时候才会生成相应的数据。

    如果要打印生成器的数据,则需要.__next__()方法

    print(c.__next__()) # 输出第一个数
    0
    print(c.__next__()) # 输出第二个数
    1
    print(c.__next__()) # 输出第三个数
    2
    print(c.__next__()) # 输出第四个数
    3
    print(c.__next__()) # 输出第五个数
    4

    如果我只需要当中的最后一个数据呢?能不能直接输出?

    抱歉,不能。而且,生成器的数据只能从前往后去访问,不能从后往前去访问,在内存中只保留一个值,也就是说,访问过的数据已经无法再次访问。

    如果生成器有很多的数据,要全部输出,有没有简便的写法?

    抱歉,没有,您只能一个一个地输出。

    当然,像上面那样不断调用.__next__()还是太坑爹了,可以用for去迭代它(生成器也是可迭代对象):

     g = (x * x for x in range(10))
    for n in g:
        print(n)

    那,,我还要生成器有卵用??

    还是有点卵用的,生成器一般依托于函数实现,比如,我先定义一个函数fib(),函数内定义了数列的推算规则

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'
    
    # 注释:
    a, b = b, a + b  相当于:
    t = (b, a + b) # t是一个tuple
    a = t[0]
    b = t[1]
    它不必写出显式变量 t

    如果给fib()传参10,它将输出一连串的数字,可以组成一个数列:

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

    此时的fib函数,已经非常接近生成器了,只需要一个yield即可,

    def fib(max):  #当函数中有yield出现时,不能将其简单视为函数,是一个生成器。
        "生成器"
        n,a,b=0,0,1
        while n<max:
            yield b  #yield保存了函数当前的中断状态,返回当前b的值
            a,b=b,a+b
            n=n+1 #计数器
        return "done"

    此时,fib(10)是一个生成器,

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

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

    来一个一个地输出它的值:

    f=fib(10)
    print(f)
    print(f.__next__())
    print(f.__next__())
    print(f.__next__())
    print(f.__next__())

    因为只能一个一个地输出,且不能得知长度,所以总会有越界的时候,会报一个异常StopIteration,导致程序停止

    所以需要捕获异常:

    while 1:
        try: #如果没有出现异常,执行下面语句
            x=next(g)
            print("g:",x)
        except StopIteration as e: #如果出现异常StopIteration,把它赋给e,执行下面的语句
            print(e.value)
            break

    前面讲到,生成器只能一个一个地取出数据,在fib函数执行过程中会中断,为什么要这样呢?有什么用吗?

    它厉害在:可以在单线程的情况下实现并发效果,举个例子:

    import time
    
    def custumer(name):
        print("{0}准备来吃包子了".format(name))
        while 1:
            baozi = yield    #每次运行到这一行时都会中断
            print("包子{0}来了,被{1}吃掉了".format(baozi,name))
    
    def producer(name):
        c1=custumer("老大")
        c2=custumer("老二")
        c1.__next__() 
        c2.__next__()    # next 只是在调用yield
        print("{0}开始做包子啦!".format(name))
        for i in range(1,15,2):
            time.sleep(1)
            print("做了两个包子")
            c1.send(i)     # send 调用yield的同时给它传值
            c2.send(i+1)
    
    producer("alex")
    View Code

    如果在自己的解释器上执行,会发现一个程序有三个任务交错切换运行,看上去就像三个任务同时在进行。

    三、迭代器

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

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

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

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

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

     

    for循环本质上时不断调用next()函数实现的:

    a=[1,2,3,4,5]
    for x in a:
        print(x)
    #完全等价于
    it=iter(a) # 将列表转化成迭代器对象
    while 1:
        try:
            x=next(it)  #获得下一个值
            print(x)
        except StopIteration:
            break #遇到StopIteration异常就跳出循环

    在文件操作时,

    for line in f:
        print(line)

    每次输出其实都是调用next()函数,在Python3中已经看不出是一个迭代器了。

     

  • 相关阅读:
    软件课设Day18
    软件课设Day17
    软件课设Day16
    2019/09/12最新进展
    2019/09/11最新进展
    2019/09/10最新进展
    2019/09/09最新进展
    2019/09/08最新进展
    2019/09/07最新进展
    2019/09/06最新进展
  • 原文地址:https://www.cnblogs.com/V587Chinese/p/9033201.html
Copyright © 2011-2022 走看看