• python中的生成器


    一 什么是生成器?

    生成器从字面理解就是生成XXX的工具,确实也是如此;在python中是这么定义生成器的:

    • 只要在python的函数定义中有yield关键字,那么该函数就是一个生成器函数,调用该函数时就会返回一个生成器对象。

    如下就是一个生成器函数

    from collections.abc import Iterable, Iterator, Generator
    
    #生成器Generator
    def my_generator_one():
        yield 1
        yield 2
    
    def my_generator_two():
        for i in range(10):
            yield i
    
    gen_one = my_generator_one()
    gen_two = my_generator_two()
    print(gen_one)
    print(gen_two)
    print("gen_one的数据类型:", type(gen_one))
    print("gen_one是否是生成器:", isinstance(gen_one, Generator))
    print("gen_two的数据类型:", type(gen_two))
    print("gen_two是否是生成器:", isinstance(gen_two, Generator))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    >>>
    <generator object my_generator_one at 0x0000019F3C76E3C8>
    <generator object my_generator_two at 0x0000019F3C76E548>
    gen_one的数据类型: <class 'generator'>
    gen_one是否是生成器: True
    gen_two的数据类型: <class 'generator'>
    gen_two是否是生成器: True
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从上面结果可以看出,只要函数定义里面有yield关键字,那么这个函数就是生成器函数,调用它们时就会得到一个生成器对象generator object。
    从外观上看,生成器函数有下面几个特点(与普通函数的区别)

    • 生成器函数的定义体里面有yield关键字,而普通函数没有
    • 生成器函数里面没有return语句,yield就相当于return,普通函数有return语句

    二 生成器,迭代器的区别与联系

    为了更好的理解生成器,生成器与迭代器的区别和联系,可以先看看我的上篇文章:python中的迭代器,可迭代对象(详细剖析)

    1.生成器和迭代器的联系
    • 所有的生成器都是迭代器,生成器是迭代器的一种

    为什么说所有的生成器都是迭代器?这是因为生成器默认实现了迭代器协议,即实现了_ _iter _ _ 方法和 _ next _ 方法,下面来验证一下

    from collections.abc import Iterable, Iterator, Generator
    
    #生成器Generator
    def my_generator_two():
        for i in range(10):
            yield i
    
    gen_two = my_generator_two()
    print("gen_two的数据类型:", type(gen_two))
    print("gen_two是否是生成器:", isinstance(gen_two, Generator))
    print("gen_two是否是迭代器:", isinstance(gen_two, Iterator))
    print(dir(gen_two))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    >>>
    gen_two的数据类型: <class 'generator'>
    gen_two是否是生成器: True
    gen_two是否是迭代器: True
    ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.生成器和迭代器的区别

    生成器和迭代器的区别如下

    • 一般而言,迭代器用于从可迭代对象中取出元素,而生成器则是‘凭空’生成元素

    关于迭代器用于从可迭代对象中取出元素可以参考:python中的迭代器,可迭代对象(详细剖析)
    ,生成器‘‘凭空’生成元素在下面的生成器实现中讲解。

    三 生成器的实现原理

    1.生成器的实现原理

    上面说过生成器函数和普通函数的主要区别在于生成器函数有yield关键字,而普通函数没有。如果理解了yield的作用,也就基本知道了生成器的原理。
    下面用生成器函数实现并返回斐波那契数列,如下

    #生成器Generator
    def fibo_generator():
        a, b = 0, 1
        for i in range(1, 100):
            yield a
            a, b = b, a+b
    
    gen = fibo_generator()
    print("第一次:", gen)
    print("第一次:", next(gen))
    print("第二次:", gen)
    print("第二次:", next(gen))
    print("第三次:", gen)
    print("第三次:", next(gen))
    print("第四次:", gen)
    print("第四次:", next(gen))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    >>>
    第一次: <generator object fibo_generator at 0x000002008ED07F48>
    第一次: 0
    第二次: <generator object fibo_generator at 0x000002008ED07F48>
    第二次: 1
    第三次: <generator object fibo_generator at 0x000002008ED07F48>
    第三次: 1
    第四次: <generator object fibo_generator at 0x000002008ED07F48>
    第四次: 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上面我们定义了一个生成器函数fibo_generator, 变量a, b是斐波那契的第一项和第二项值,然后用yield返回斐波那契的前一百项,不是一次性返回,而是每次返回一个。

    从结果可以看到,每调一次fibo_generator函数,就会返回一个生成器,因为生成器又是迭代器。所有我们可以用 next() 来取出生成器生成的值,结果显而易见,feibo_generator每次生成并返回对应的斐波那契数列的数。

    以上的这些过程都是 yield 在其中起的作用,具体如下

    • 第一次执行生成器函数时,从头按顺序执行,当碰到yield关键字时 ,生成器函数会暂停执行该函数的后续代码,记住此时的位置,并且返回一个生成器对象generator object(相当于return),生成器中存储了生成的值。
    • 下次再调用该生成器函数时,程序将从上一次暂停的地方继续往下执行,如果生成器无法继续生成下一个值,并会抛出StopIteration异常,生成器函数结束执行。

    这两点便是生成器(yield)的主要原理。下面这个案例更能具体体现

    def my_generator():
    
        print("start A")
        yield 'a'
        print("start B")
        yield 'b'
        print("start C")
        yield 'c'
    
    gen = my_generator()
    for i in gen:
        print(i)
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    >>>
    start A
    a
    start B
    b
    start C
    c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从结果可以看出,效果如出一辙。不过这里不是直接使用的next()函数从生成器里面取的值,而是利用for关键字来迭代取的,这就是生成器又是迭代器的原因(迭代器可以使用for来迭代取值)。

    2.yield from

    当我们需要把多个生成器合并一个生成器时,可以这样写

    str = 'ABC'
    tup = (1, 2, 3)
    
    def my_generator(arg1, arg2):
        for i in arg1:
            yield i
        for j in arg2:
            yield j
    
    gen = my_generator(str, tup)
    for i in gen:
        print(i, end=' ')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    >>>
    A B C 1 2 3 
    
    • 1
    • 2

    这样写可以,但是当有多个生成器组成时,上面的写法就会变的比较臃肿,这时python又提供了另外一种写法,即yield from,它的作用完全同每个for循环一样,如下

    str = 'ABC'
    tup = (1, 2, 3)
    
    def my_generator(arg1, arg2):
        yield from arg1
        yield from arg2
    
    gen = my_generator(str, tup)
    for i in gen:
        print(i, end=' ')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.生成器的应用

    通过上面的一通讲解,应该明白了生成器的作用是:要一次数据,就生成一次。,而不是像列表一样,一次性存储所有数据。
    由于生成器取一次,生成一次的特点,这使得它在节省内存方面非常优秀,尤其是在处理大数据流方面。
    下面简单的比较下它和列表所占内存的大小

    import sys
    
    my_list = [x for x in range(10000)]
    
    def my_generator():
        for i in range(10000):
            yield i
    
    print("列表:", sys.getsizeof(my_list))
    print("生成器:", sys.getsizeof(my_generator()))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    列表: 87624
    生成器: 120
    
    • 1
    • 2

    结果显而易见。



    以上就是本人对python生成器的一些思考和拙见,如有误,还望指出。如有帮助,请点个赞。

  • 相关阅读:
    Swift制作打包framework
    MATLAB解析和保存ini文件
    【网站架构】同是响应式布局为什么我的页面布局是错乱的?布局工作占了大部分前端工作量怎样才能做好响应式布局?一份代码如何适配多个显示端?
    LeetCode LCR 103. 零钱兑换【完全背包,恰好装满背包的最小问题】中等
    批量插入优化
    常用算法(七)——克鲁斯卡尔算法
    [c语言]这c语言代码就像做完形填空一样
    PyTorch离线安装
    【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡
    “2024成都国际电子信息产业展览会”新西部、新重构、新机遇
  • 原文地址:https://blog.csdn.net/qq_44690947/article/details/127344601