Python進階——如何正確使用yield?

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閱讀本文大約需要 10 分鐘。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Python 開發中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 關鍵字的使用其實較爲頻繁,例如大集合的生成,簡化代碼結構、協程與併發都會用到它。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,你是否真正瞭解 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的運行過程呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這篇文章,我們就來看一下 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的運行流程,以及在開發中哪些場景適合使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"生成器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在一個方法內,包含了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 關鍵字,那麼這個函數就是一個「生成器」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器其實就是一個特殊的迭代器,它可以像迭代器那樣,迭代輸出方法內的每個元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你還不清楚「迭代器」是什麼,可以參考我寫的這篇文章:","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/21c4bdc50e084b08dff0e40dc","title":""},"content":[{"type":"text","text":"Python技術進階——迭代器、可迭代對象、生成器","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看一個包含 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 關鍵字的方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\n# 生成器\ndef gen(n):\n for i in range(n):\n yield i\n\ng = gen(5) # 創建一個生成器\nprint(g) # \nprint(type(g)) # \n\n# 迭代生成器中的數據\nfor i in g:\n print(i)\n \n# Output:\n# 0 1 2 3 4","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意,在這個例子中,當我們執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g = gen(5)","attrs":{}}],"attrs":{}},{"type":"text","text":" 時,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"gen","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的代碼其實並沒有執行,此時我們只是創建了一個「生成器對象」,它的類型是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generator","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後,當我們執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for i in g","attrs":{}}],"attrs":{}},{"type":"text","text":",每執行一次循環,就會執行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 處,返回一次 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 後面的值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個迭代過程是和迭代器最大的區別。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"換句話說,如果我們想輸出 5 個元素,在創建生成器時,這個 5 個元素其實還並沒有產生,什麼時候產生呢?只有在執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 循環遇到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 時,纔會依次生成每個元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,生成器除了和迭代器一樣實現迭代數據之外,還包含了其他方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.__next__()","attrs":{}}],"attrs":{}},{"type":"text","text":":執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 時調用此方法,每次執行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 就會停止,然後返回 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 後面的值,如果沒有數據可迭代,拋出 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"StopIterator","attrs":{}}],"attrs":{}},{"type":"text","text":" 異常,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 循環結束","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.send(value)","attrs":{}}],"attrs":{}},{"type":"text","text":":外部傳入一個值到生成器內部,改變 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 前面的值","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.throw(type[, value[, traceback]])","attrs":{}}],"attrs":{}},{"type":"text","text":":外部向生成器拋出一個異常","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"generator.close()","attrs":{}}],"attrs":{}},{"type":"text","text":":關閉生成器","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過使用生成器的這些方法,我們可以完成很多有意思的功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"__","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"next","attrs":{}},{"type":"text","text":"__","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來看生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"__next__","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,我們看下面這個例子。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen(n):\n for i in range(n):\n print('yield before')\n yield i\n print('yield after')\n\ng = gen(3) # 創建一個生成器\nprint(g.__next__()) # 0\nprint('----')\nprint(g.__next__()) # 1\nprint('----')\nprint(g.__next__()) # 2\nprint('----')\nprint(g.__next__()) # StopIteration\n\n# Output:\n# yield before\n# 0\n# ----\n# yield after\n# yield before\n# 1\n# ----\n# yield after\n# yield before\n# 2\n# ----\n# yield after\n# Traceback (most recent call last):\n# File \"gen.py\", line 16, in \n# print(g.__next__()) # StopIteration\n# StopIteration","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個例子中,我們定義了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"gen","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,這個方法包含了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 關鍵字。然後我們執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g = gen(3)","attrs":{}}],"attrs":{}},{"type":"text","text":" 創建一個生成器,但是這次沒有執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"for","attrs":{}}],"attrs":{}},{"type":"text","text":" 去迭代它,而是多次調用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"next","attrs":{}},{"type":"text","text":"()","attrs":{}}],"attrs":{}},{"type":"text","text":" 去輸出生成器中的元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們看到,當執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.__next__()","attrs":{}}],"attrs":{}},{"type":"text","text":"時,代碼就會執行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 處,然後返回 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 後面的值,如果繼續調用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.__next__()","attrs":{}}],"attrs":{}},{"type":"text","text":",注意,你會發現,這次執行的開始位置,是上次 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 結束的地方,並且它還保留了上一次執行的上下文,繼續向後迭代。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的作用,在迭代生成器時,每一次執行都可以保留上一次的狀態,而不是像普通方法那樣,遇到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"return","attrs":{}}],"attrs":{}},{"type":"text","text":" 就返回結果,下一次執行只能再次重複上一次的流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器除了能保存狀態之外,我們還可以通過其他方式,改變其內部的狀態,這就是下面要講的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"throw","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"send","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的例子中,我們只展示了在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 後有值的情況,其實還可以使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = yield i","attrs":{}}],"attrs":{}},{"type":"text","text":" 這種語法,我們看下面的代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen():\n i = 1\n while True:\n j = yield i\n i *= 2\n if j == -1:\n break","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時如果我們執行下面的代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"for i in gen():\n print(i)\n time.sleep(1)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果會是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1 2 4 8 16 32 64 ...","attrs":{}}],"attrs":{}},{"type":"text","text":" 一直循環下去, 直到我們殺死這個進程才能停止。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這段代碼一直循環的原因在於,它無法執行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j == -1","attrs":{}}],"attrs":{}},{"type":"text","text":" 這個分支裏 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"break","attrs":{}}],"attrs":{}},{"type":"text","text":" 出來,如果我們想讓代碼執行到這個地方,如何做呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏就要用到生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法了,","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"send","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 方法可以把外部的值傳入生成器內部,從而改變生成器的狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼可以像下面這樣寫:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"g = gen() # 創建一個生成器\nprint(g.__next__()) # 1\nprint(g.__next__()) # 2\nprint(g.__next__()) # 4\n# send 把 -1 傳入生成器內部 走到了 j = -1 這個分支\nprint(g.send(-1)) # StopIteration 迭代停止","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們執行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.send(-1)","attrs":{}}],"attrs":{}},{"type":"text","text":" 時,相當於把 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"-1","attrs":{}}],"attrs":{}},{"type":"text","text":" 傳入到了生成器內部,然後賦值給了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 前面的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j","attrs":{}}],"attrs":{}},{"type":"text","text":",此時 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = -1","attrs":{}}],"attrs":{}},{"type":"text","text":",然後這個方法就會 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"break","attrs":{}}],"attrs":{}},{"type":"text","text":" 出來,不會繼續迭代下去。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"throw","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"外部除了可以向生成器內部傳入一個值外,還可以傳入一個異常,也就是調用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"throw","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen():\n try:\n yield 1\n except ValueError:\n yield 'ValueError'\n finally:\n print('finally')\n\ng = gen() # 創建一個生成器\nprint(g.__next__()) # 1\n# 向生成器內部傳入異常 返回ValueError\nprint(g.throw(ValueError))\n\n# Output:\n# 1\n# ValueError\n# finally","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個例子創建好生成器後,使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"g.throw(ValueError)","attrs":{}}],"attrs":{}},{"type":"text","text":" 的方式,向生成器內部傳入了一個異常,走到了生成器異常處理的分支邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"close","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法也比較簡單,就是手動關閉這個生成器,關閉後的生成器無法再進行操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":">>> g = gen()\n>>> g.close() # 關閉生成器\n>>> g.__next__() # 無法迭代數據\nTraceback (most recent call last):\n File \"\", line 1, in \nStopIteration","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法我們在開發中使用得比較少,瞭解一下就好。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用場景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 和生成器的使用方式,那麼 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 和生成器一般用在哪些業務場景中呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我介紹幾個例子,分別是大集合的生成、簡化代碼結構、協程與併發,你可以參考這些使用場景來使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"大集合的生成","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想生成一個非常大的集合,如果使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 創建一個集合,這會導致在內存中申請一個很大的存儲空間,例如想下面這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef big_list():\n result = []\n for i in range(10000000000):\n result.append(i)\n return result\n\n# 一次性在內存中生成大集合 內存佔用非常大\nfor i in big_list():\n print(i)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種場景,我們使用生成器就能很好地解決這個問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲生成器只有在執行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 時纔會迭代數據,這時只會申請需要返回元素的內存空間,代碼可以這樣寫:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef big_list():\n for i in range(10000000000):\n yield i\n\n# 只有在迭代時 才依次生成元素 減少內存佔用\nfor i in big_list():\n print(i)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"簡化代碼結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在開發時還經常遇到這樣一種場景,如果一個方法要返回一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":",但這個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 是多個邏輯塊組合後才能產生的,這就會導致我們的代碼結構變得很複雜:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen_list():\n # 多個邏輯塊 組成生成一個列表\n result = []\n for i in range(10):\n result.append(i)\n for j in range(5):\n result.append(j * j)\n for k in [100, 200, 300]:\n result.append(k)\n return result\n \nfor item in gen_list():\n print(item)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種情況下,我們只能在每個邏輯塊內使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"append","attrs":{}}],"attrs":{}},{"type":"text","text":" 向 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 中追加元素,代碼寫起來比較囉嗦。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時如果使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 來生成這個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":",代碼就簡潔很多:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef gen_list():\n # 多個邏輯塊 使用yield 生成一個列表\n for i in range(10):\n yield i\n for j in range(5):\n yield j * j\n for k in [100, 200, 300]:\n yield k\n \nfor item in gen_list():\n print(i)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 後,就不再需要定義 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 類型的變量,只需在每個邏輯塊直接 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 返回元素即可,可以達到和前面例子一樣的功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們看到,使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的代碼更加簡潔,結構也更清晰,另外的好處是隻有在迭代元素時才申請內存空間,降低了內存資源的消耗。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"協程與併發","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一種場景是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 使用非常多的,那就是「協程與併發」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們想提高程序的執行效率,通常會使用多進程、多線程的方式編寫程序代碼,最常用的編程模型就是「生產者-消費者」模型,即一個進程 / 線程生產數據,其他進程 / 線程消費數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開發多進程、多線程程序時,爲了防止共享資源被篡改,我們通常還需要加鎖進行保護,這樣就增加了編程的複雜度。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Python 中,除了使用進程和線程之外,我們還可以使用「協程」來提高代碼的運行效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什麼是協程?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單來說,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"由多個程序塊組合協作執行的程序,稱之爲「協程」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而在 Python 中使用「協程」,就需要用到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 關鍵字來配合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可能這麼說還是太好理解,我們用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 實現一個協程生產者、消費者的例子:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"python"},"content":[{"type":"text","text":"# coding: utf8\n\ndef consumer():\n i = None\n while True:\n # 拿到 producer 發來的數據\n j = yield i \n print('consume %s' % j)\n\ndef producer(c):\n c.__next__()\n for i in range(5):\n print('produce %s' % i)\n # 發數據給 consumer\n c.send(i)\n c.close()\n\nc = consumer()\nproducer(c)\n\n# Output:\n# produce 0\n# consume 0\n# produce 1\n# consume 1\n# produce 2\n# consume 2\n# produce 3\n# consume 3\n...","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個程序的執行流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"c = consumer()","attrs":{}}],"attrs":{}},{"type":"text","text":" 創建一個生成器對象","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"producer(c)","attrs":{}}],"attrs":{}},{"type":"text","text":" 開始執行,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.__next()__","attrs":{}}],"attrs":{}},{"type":"text","text":" 會啓動生成器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 直到代碼運行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = yield i","attrs":{}}],"attrs":{}},{"type":"text","text":" 處,此時 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 第一次執行完畢,返回","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 函數繼續向下執行,直到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.send(i)","attrs":{}}],"attrs":{}},{"type":"text","text":" 處,這裏利用生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,向 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 發送數據","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 函數被喚醒,從 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j = yield i","attrs":{}}],"attrs":{}},{"type":"text","text":" 處繼續開始執行,並且接收到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 傳來的數據賦值給 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"j","attrs":{}}],"attrs":{}},{"type":"text","text":",然後打印輸出,直到再次執行到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 處,返回","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 繼續循環執行上面的過程,依次發送數據給 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cosnumer","attrs":{}}],"attrs":{}},{"type":"text","text":",直到循環結束","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"最終 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.close()","attrs":{}}],"attrs":{}},{"type":"text","text":" 關閉 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 生成器,程序退出","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個例子中我們發現,程序在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"producer","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consumer","attrs":{}}],"attrs":{}},{"type":"text","text":" 這 2 個函數之間","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"來回切換","attrs":{}},{"type":"text","text":"執行,相互協作,完成了生產任務、消費任務的業務場景,最重要的是,整個程序是在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"單進程單線程","attrs":{}},{"type":"text","text":"下完成的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個例子用到了上面講到的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":"、生成器的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"__next__","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"send","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"close","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法。如果不好理解,你可以多看幾遍這個例子,最好自己測試一下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們使用協程編寫生產者、消費者的程序時,它的好處是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個程序運行過程中無鎖,不用考慮共享變量的保護問題,降低了編程複雜度","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序在函數之間來回切換,這個過程是用戶態下進行的,不像進程 / 線程那樣,會陷入到內核態,這就減少了內核態上下文切換的消耗,執行效率更高","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Python 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"yield","attrs":{}}],"marks":[{"type":"strong"}],"attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 和生成器實現了協程的編程方式,爲程序的併發執行提供了編程基礎。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Python 中的很多第三方庫,都是基於這一特性進行封裝的,例如 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"gevent","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tornado","attrs":{}}],"attrs":{}},{"type":"text","text":",它們都大大提高了程序的運行效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結一下,這篇文章我們主要講了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 的使用方式,以及生成器的各種特性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成器是一種特殊的迭代器,它除了可以迭代數據之外,在執行時還可以保存方法中的狀態,除此之外,它還提供了外部改變內部狀態的方式,把外部的值傳入到生成器內部。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 和生成器的特性,我們在開發中可以用在大集成的生成、簡化代碼結構、協程與併發的業務場景中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Python 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yield","attrs":{}}],"attrs":{}},{"type":"text","text":" 也是實現協程和併發的基礎,它提供了協程這種用戶態的編程模式,提高了程序運行的效率。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c1/c10b027be63b0c5c1ffc36a76a4a974b.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"關注「水滴與銀彈」公衆號,7年資深後端研發,和你分享更多優質技術乾貨。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章