yield from
是Python3.3新出現(xiàn)的句法
替代內(nèi)層for循環(huán)
如果生成器函數(shù)需要產(chǎn)出另一個生成器生成的值么库,傳統(tǒng)的解決方法是使用嵌套的for循環(huán):
>>> def chain(*iterables):
... for it in iterables:
... for i in it:
... yield i
>>> s = 'ABC'
>>> t = tuple(range(3))
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
chain 生成器函數(shù)把操作依次交給接收到的各個可迭代對象處理酷愧。
Python3.3之后引入了新語法:
>>> def chain(*iterables):
... for i in iterables:
... yield from i
...
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
-
yield from
完全代替了內(nèi)層的 for 循環(huán)。 -
yield from x
表達式對 x 對象所做的第一件事是,調(diào)用 iter(x)履植,從中獲取迭代器勉耀。因
此,x 可以是任何可迭代的對象仗阅。 - 在這個示例中使用
yield from
代碼讀起來更順暢,不過感覺更像是語法糖国夜。
上面這個例子看上去比較簡單(傳統(tǒng)意義上說因為我們只是for循環(huán)一次就完事减噪,因為只嵌套了一層),我們再來看幾個yield from
的例子车吹。
例子1:我們有一個嵌套型的序列筹裕,想將它扁平化處理為一列單獨的值。
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
items = [1, 2, [3, 4, [5, 6], 7], 8]
for x in flatten(items):
print(x)
# output:
1 2 3 4 5 6 7 8
-----------------------------------------------
items = ['Dave', 'Paula', ['Thomas', 'Lewis']]
for x in flatten(items):
print(x)
# output:
Dave
Paula
Thomas
Lewis
-
collections.Iterable
是一個抽象基類窄驹,我們用isinstance(x, Iterable)
檢查某個元素是否是可迭代的.如果是的話,那么就用yield from
將這個可迭代對象作為一種子例程進行遞歸朝卒。最終返回結(jié)果就是一個沒有嵌套的單值序列了。 - 代碼中額外的參數(shù)
ignore types
和檢測語句isinstance(x, ignore types)
用來將字符
串和字節(jié)排除在可迭代對象外乐埠,防止將它們再展開成單個的字符抗斤。 - 如果這里不用
yield from
的話,那么就需要另外一個for來嵌套丈咐,并不是一種優(yōu)雅的操作
例子2:利用一個Node類來表示樹結(jié)構(gòu)
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
-
__iter__
代表一個Pyton的迭代協(xié)議瑞眼,返回一個迭代器對象,就能迭代了 -
depth_frist
返回一個生成器,仔細體會其中的yield
與yield from
用法
上面兩個例子無論是樹還是嵌套序列棵逊,都比較復雜伤疙,觀察這里yield from
跟的是什么,跟的是函數(shù),生成器函數(shù),而且都是在函數(shù)內(nèi)遞歸。雖然我也不是理解的很透徹 =,= 辆影。但現(xiàn)在應該知道掩浙,這是yield from
一種常用的方法了(認真體會,手動滑稽)秸歧。
打開雙通道
如果 yield from
結(jié)構(gòu)唯一的作用是替代產(chǎn)出值的嵌套 for 循環(huán)厨姚,這個結(jié)構(gòu)很有可能不會添加到 Python 語言中。yield from
結(jié)構(gòu)的本質(zhì)作用無法通過簡單的可迭代對象說明键菱,而要發(fā)散思維谬墙,使用嵌套的生成器今布。
yield from 的主要功能是打開雙向通道,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來拭抬,這樣二者可以直接發(fā)送和產(chǎn)出值部默,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量處理異常的樣板代碼造虎。有了這個結(jié)構(gòu)傅蹂,協(xié)程可以通過以前不可能的方式委托職責。
這里有張詳細圖來說明三者關系:http://flupy.org/resources/yield-from.pdf
這里只是大概展示下三者關系算凿,具體的內(nèi)容可以去上面那個網(wǎng)址仔細觀察份蝴。
例子就不展開了,有興趣的童鞋可以去 Fluent Python這本書上 查看 示例16-17氓轰。并且結(jié)合示例圖好好體會(我也有待好好體會)
總結(jié):
-
yield from
常用來代替內(nèi)層for循環(huán) 與 打開雙通道 - 但是大部分情況下
yield from
并不單獨使用婚夫,而是伴隨著asyncio
庫使用,實現(xiàn)異步操作(一異步操作后面講) - 從Python 3.5開始引入了新的語法
async
和await
署鸡,而await替代的就是yield from
(為了不與實現(xiàn)內(nèi)層for循環(huán)的yield from
誤解)
參考資料
David beazley協(xié)程
Fluent Python
Python Cookbook