面試題-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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM