zoukankan      html  css  js  c++  java
  • 面试题-python 什么是生成器(generator)?

    前言

    在 Python 中,带有 yield 的函数在 Python 中被称之为 generator(生成器)。
    跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

    生成器 yield 用法

    函数里面的 return 应该都知道,当函数遇到return 后就返回某个值,不会继续往下了。
    yield 可以理解成return ,但不能完成等于return ,当程序运行到yield 后, 会返回某个值,返回之后程序就不再往下运行了。

    以下示例代码

    def function():
        print("start.....")
        s = 1
        while True:
            s += 1
            print("s 的值:", s)
            yield s
            print("yield 后s 的值:", s)
    
    a = function()
    print(a)   # <generator object function at 0x000001A039955258>
    
    print(next(a))
    

    运行结果

    <generator object function at 0x0000021D3A0351A8>
    start.....
    s 的值: 2
    2
    

    首先带有yield的函数是一个生成器,当执行a = function() 时,不会打印 "start....." , 此时反回的是一个 generator object。
    生成器是一个返回迭代器的函数,只能用于迭代操作,简单点理解生成器就是一个迭代器。
    既然生成器就是迭代器,那就可以用next() 函数去执行,当第一次调用next() 遇到yield 相当于return 那么函数结束了。此时返回yield 后面的s,也就是返回值是2
    第二次调用next()会继续执行yield后面的代码,回到while 循环,直到遇到yield结束

    def function():
        print("start.....")
        s = 1
        while True:
            s += 1
            print("s 的值:", s)
            yield s
            print("yield 后s 的值:", s)
    
    a = function()
    print(a)   # <generator object function at 0x000001A039955258>
    
    print(next(a))
    # 第二次调用next()
    print(next(a))
    

    结果返回

    <generator object function at 0x0000019E07935258>
    start.....
    s 的值: 2
    2
    yield 后s 的值: 2
    s 的值: 3
    3
    

    再看一个关于 yield 的例子

    def demoIterator():
        '''yield 生成器demo'''
        print("I'm in the first call of next()")
        yield 1
        print("I'm in the second call of next()")
        yield 3
        print("I'm in the third call of next()")
        yield 9
    
    a = demoIterator()
    print(a)  # <generator object demoIterator at 0x00000143A25A5258>
    print(next(a))  # return 1
    print(next(a))  # return 3
    print(next(a))  # return 9
    # 继续next()
    print(next(a))  # StopIteration
    

    运行结果

    <generator object demoIterator at 0x000001E66E1F5258>
    I'm in the first call of next()
    1
    I'm in the second call of next()
    3
    I'm in the third call of next()
    9
    Traceback (most recent call last):
      File "D:xx.py", line 32, in <module>
        print(next(a))  # StopIteration
    StopIteration
    

    通过上面的例子可以看出,每调用一次 next() 都会执行到 yield 停止,然后返回 yield 后面的值,当执行到最后,没有的时候继续用 next() 会抛异常 StopIteration

    生成器可以用于迭代操作,也能用 for 遍历

    a = demoIterator()
    print(a)  # <generator object demoIterator at 0x00000143A25A5258>
    
    # for 遍历
    for i in a:
        print(i)
    

    生成器 send 方法

    生成器 generator 有两个方法需注意,一个是__next__() ,另外一个是send()方法
    __next__() 和 next() 函数功能是一样的

    a = demoIterator()
    print(a)  # <generator object demoIterator at 0x00000143A25A5258>
    
    print(a.__next__())
    print(a.__next__())
    print(a.__next__())
    运行结果
    <generator object demoIterator at 0x0000024EB9985258>
    I'm in the first call of next()
    1
    I'm in the second call of next()
    3
    I'm in the third call of next()
    9
    

    yield 的作用有2个,第一个是我们上面看到的可以类似于 return ,返回一个值出来。
    另外一个功能是可以接收外部传过去的值,给 yield 传值需用到send() 方法

    def demoIterator():
        '''yield 生成器demo'''
        print("I'm in the first call of next()")
        name1 = yield 1
        print("my name is :", name1)
        print("I'm in the second call of next()")
        name2 = yield 3
        print("my name is :", name2)
        print("I'm in the third call of next()")
        name3 = yield 9
        print("my name is :", name3)
    
    a = demoIterator()
    print(a)  # <generator object demoIterator at 0x00000143A25A5258>
    
    print(a.__next__())
    print(a.__next__())
    print(a.__next__())
    

    运行结果

    <generator object demoIterator at 0x000001B7797E51A8>
    I'm in the first call of next()
    1
    my name is : None
    I'm in the second call of next()
    3
    my name is : None
    I'm in the third call of next()
    9
    

    name 1的 值是yield 给过来的,这时候可以把yield 当成一个变量,这个变量的值默认是None,所以打印:my name is : None
    接下来通过send() 方法给yield 赋值。

    a = demoIterator()
    print(a)  # <generator object demoIterator at 0x00000143A25A5258>
    
    print(a.__next__())
    print(a.send("yoyo1"))
    print(a.send("yoyo2"))
    

    运行结果

    <generator object demoIterator at 0x000002BE7D645048>
    I'm in the first call of next()
    1
    my name is : yoyo1
    I'm in the second call of next()
    3
    my name is : yoyo2
    I'm in the third call of next()
    9
    

    我们可以这样理解:send()方法把值给到yield, yield赋值给name1和name2,于是就可以看到上面的结果了。
    这里需要注意的是当调用send() 方法的时候,实际上是有2个功能的:

    • 1.赋值给到yield的
    • 2.执行了一次.__next__()方法,或next()函数

    于是我们会想,send(None)是不是就等价于.__next__()方法 呢?

    a = demoIterator()
    print(a)  # <generator object demoIterator at 0x00000143A25A5258>
    
    print(a.send(None))
    print(a.send("yoyo1"))
    print(a.send("yoyo2"))
    

    运行结果

    <generator object demoIterator at 0x0000016F8C1651A8>
    I'm in the first call of next()
    1
    my name is : yoyo1
    I'm in the second call of next()
    3
    my name is : yoyo2
    I'm in the third call of next()
    9
    

    需要注意的是,第一次只能是send(None), 不能send()其它值,否则会抛异常TypeError: can't send non-None value to a just-started generator

    a = demoIterator()
    print(a.send("yoyo"))
    
    抛异常
    Traceback (most recent call last):
      File "D:xx.py", line 31, in <module>
        print(a.send("yoyo"))
    TypeError: can't send non-None value to a just-started generator
    

    至于为什么要先传递一个None进去,可以看一下官方文档

    
    Because generator-iterators begin execution at the top of the
    generator's function body, there is no yield expression to receive
    a value when the generator has just been created.  Therefore,
    calling send() with a non-None argument is prohibited when the
    generator iterator has just started, and a TypeError is raised if
    this occurs (presumably due to a logic error of some kind).  Thus,
    before you can communicate with a coroutine you must first call
    next() or send(None) to advance its execution to the first yield
    

    因此我们在使用生成器的时候,必须要先执行一次next()方法。

    斐波那契数列

    斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13,特别指出:第0项是0,第1项是第一个1。从第三项开始,每一项都等于前两项之和
    求出小于100 的所有的斐波那契数列

    说到生成器不得不提到斐波那契数列, 前面一篇学了迭代器来解决,但是代码量有点复杂,用带 yield 的生成器函数更简单一点。

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    
    
    def numbers(max):
        n1, n2, count = 0, 1, 1
        while count < max:
            print(count, end=" ")
            n1, n2 = n2, count
            count = n1+n2
            
    if __name__ == '__main__':
        numbers(100)
    

    上面的函数虽然能打印出内容,但是我们拿不到函数的返回值,我们希望函数每次的返回值可以拿到,于是可以把print 换成 yield ,把结果 return 出来

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    
    
    def numbers(max):
        n1, n2, count = 0, 1, 1
        while count < max:
            yield count
            # print(count, end=" ")
            n1, n2 = n2, count
            count = n1+n2
    
    if __name__ == '__main__':
        a = numbers(100)
        print(a)
        for i in a:
            print(i)
    

    读取大文件

    另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。
    好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

    def read_file(fpath): 
       BLOCK_SIZE = 1024 
       with open(fpath, 'rb') as f: 
           while True: 
               block = f.read(BLOCK_SIZE) 
               if block: 
                   yield block 
               else: 
                   return
    

    参考文档推荐-最简单最清晰https://blog.csdn.net/mieleizhi0522/article/details/82142856/
    参考文档推荐-菜鸟教程https://www.runoob.com/w3cnote/python-yield-used-analysis.html
    参考文档https://yasoob.me/2013/09/29/the-python-yield-keyword-explained/

  • 相关阅读:
    ffmpeg之AVFrame
    ffmpeg之samplefmt
    音视频基本概念
    cmake函数 file
    ffmpeg之AVPacket
    ffmpeg之AVFormatContext
    存储格式:packed和planar
    ffmpeg之channel_layout
    cmake函数: get_filename_component
    ffmpeg整体结构
  • 原文地址:https://www.cnblogs.com/yoyoketang/p/14464031.html
Copyright © 2011-2022 走看看