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

    生成器和迭代器

    列表生成式

    现在有这么一个需求,要将[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]这个列表中的每个元素都加1,那么需要怎么实现呢?你可能会想到这几种方式:

    
    a = list(range(0,10))
    # print(a)
    
    
    # 1.二逼青年版
    b = []
    for i in a:
        b.append(i+1)
    
    print(b)
    
    
    # 2.普通青年版
    
    c = a
    for index,i in enumerate(c):
        a[index] += 1
    
    print(c)
    
    
    # 3.文艺青年版
    d = a
    d = map(lambda x:x,d)
    for i in d:
        print(i)
    
    

    其实还有一种写法,如下:

    # 4.装逼青年版
    e = a
    e = [i+1 for i in range(10)]
    print(e)
    

    通过列表生成式,我们可以直接创建一个列表。但是受到内存限制,列表容量肯定是有限制的,就像是递归,最大递归深度python就对其作了限制。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅想访问的是前几个元素的话,那么后面绝大多数元素占用的空间就是浪费了。

    就比如说:我们家是卖手机的,我们找到了专门制作手机的厂商,告诉他,我这个手机要1000万台,你们给我做吧,然而厂商考虑,和你说:这款手机我不知道能不能卖这么多,你看我们就1000台分一批给你生产出来,如果你还要,那么再给你生产1000台,你考虑到这也是一个问题,所以便同意了。

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

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

    l = [x*x for x in range(10)]
    print(l)  # l = [x*x for x in range(10)]
    
    g = (x*x for x in range(10))
    print(g)
    
    运行结果为:
    <generator object <genexpr> at 0x000001E28508FFC0>
    

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

    我们可以直接打印出来l列表中的每一个元素,但是怎么打印出generator的每一个元素呢?

    如果需要打印的话,我们就需要使用next()函数获得generator的下一个返回值:

    g = (x*x for x in range(10))
    print(g)
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    
    运行结果为:
    <generator object <genexpr> at 0x000002586EF82518>
    0
    1
    4
    9
    16
    

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

    当然,每需要获取下一个元素的值,就需要使用一次next的话,就有点太变态了,所以,正确的方法时for循环,因为generator也是可迭代的

    g = (x*x for x in range(10))
    for i in g:
        print(i)
        
    运行结果为:
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81
    

    genera足够强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现,著名的斐波那契数列用列表生成式写不出来,但是用函数把它打印出来却很容易:

    def fib(max):
        n,a,b = 0,1,1  # 分别赋值
        while n < max:  # 条件判断
            print(b)  
            a,b = b,a+b  # 赋值,把b赋值给a,把a+b赋值给b,在这里需要注意的是,是相加之前的结果,不是把b赋值给a后,然后再相加
            n += 1
        return 'Done'
    
    fib(10)
    
    运行结果如下:
    1
    2
    3
    5
    8
    13
    21
    34
    55
    89
    

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

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

    def fib(max):
        n,a,b = 0,1,1
        while n < max:
            yield b
            # 出现了yield就相当于把这段程序变成了生成器
            # yield把值返回到外部,而且不用终止
            # yield把函数的执行过程冻结到这一步,并且把b的值返回给外面的next()
            a,b = b,a+b
            n += 1
            
        return 'Done'
    
    f = fib(10)
    print(next(f))
    print(next(f))
    print(next(f))
    print(next(f))
    print(next(f))
    print(next(f))
    
    运行结果如下
    1
    2
    3
    5
    8
    13
    

    想要获取下一个元素的值,用next就可以了

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

    def fib(max):
        n,a,b = 0,1,1
        while n < max:
            yield b
            a,b = b,a+b
            n += 1
    
        return 'Done'
    data = fib(10)
    print(data)
    
    print(data.__next__())
    print('干点别的事')
    print(data.__next__())
    
    运行结果如下:
    <generator object fib at 0x0000020D5B02FFC0>
    1
    干点别的事
    2
    

    在上面的fib例子中,我们在循环过程中不断调用yield,就会不断中断,当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们都是使用for循环来获取返回值

    def fib(max):
        n,a,b = 0,1,1
        while n < max:
            yield b
            a,b = b,a+b
            n += 1
    
        return 'Done'
    data = fib(10)
    for i in data:
        print(i)
        
    运行结果如下:
    1
    2
    3
    5
    8
    13
    21
    34
    55
    89
    

    但是我们发现了,如果使用for循环的话,根本拿不到generator的return语句返回值。所以如果想拿到返回的值的话,必须要捕捉异常

    def fib(max):
        n,a,b = 0,1,1
        while n < max:
            yield b
            a,b = b,a+b
            n += 1
    
        return 'Done'
    data = fib(10)
    while True:
        try:
            x = next(data)
            print('data:',x)
    
        except StopIteration as e:
            print('Generator return value:',e.value)
            break
            
    运行结果:
    data: 1
    data: 2
    data: 3
    data: 5
    data: 8
    data: 13
    data: 21
    data: 34
    data: 55
    data: 89
    

    迭代器

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

    一类是集合数据类型,如list、tuple、dict、set、str等
    另一类是generator,包括生成器和带yield 的 generator fgunction等

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

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

    In [1]: from  collections import Iterable
    
    
    
    In [2]: isinstance([],Iterable)
    In [2]: isinstance([],Iterable)
    Out[2]: True
    
    In [3]: isinstance((),Iterable)
    Out[3]: True
    
    In [4]: isinstance({},Iterable)
    Out[4]: True
    
    In [6]: isinstance('abc',Iterable)
    Out[6]: True
    
    In [7]: isinstance((x for x in range(10)),Iterable)
    Out[7]: True
    
    

    而生成器不但可以作用域for循环,还可以被next()函数不断调用并返回下一个值,直到抛出错误

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

    In [1]: from collections import Iterator
    
    In [2]: isinstance((),Iterator)
    Out[2]: False
    
    In [3]: isinstance([],Iterator)
    Out[3]: False
    
    In [4]: isinstance({},Iterator)
    Out[4]: False
    
    In [5]: isinstance((x for x in range(10)),Iterator)
    Out[5]: True
    
    

    生成器都是迭代器,但列表、元组、字典虽然是可迭代对象,但不是迭代器

    把列表、元组、字典等可迭代对象转换成迭代器可以使用iter()函数

    In [6]: isinstance(iter([]),Iterator)
    Out[6]: True
    
    In [7]: isinstance(iter({}),Iterator)
    Out[7]: True
    
    

    你可能会问,为什么List,tuole,dict等数据类型不是迭代器呢?

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

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

  • 相关阅读:
    Python语言程序设计基础(3)—— 基本数据类型
    Python语言程序设计基础(2)—— Python程序实例解析
    Python语言程序设计基础(1)—— 程序设计基本方法
    Codeforces Round #513
    Codeforces Round #514 (Div. 2)
    面试必备:Java 原子操作的实现原理[精品长文]
    最常见的Java面试题及答案汇总(一)
    Java面试题大汇总(附答案)
    Java快速排序和归并排序详解
    Java面试官最常问的volatile关键字
  • 原文地址:https://www.cnblogs.com/xiaoyafei/p/9074340.html
Copyright © 2011-2022 走看看