一日一技:一個生成器如何當兩個用?
- 2019 年 11 月 7 日
- 筆記
攝影:產品經理
買單:kingname
我們知道,Python 裡面的生成器只能被消費一次,例如下面的程式碼:
def name_generator(): for name in ['產品經理', 'kingname']: yield name def say_hello(g): print('hello 函數開始運行') for name in g: print('hello', name) print('hello 函數運行完成') def say_hi(g): print('hi函數開始運行') for name in g: print('hi', name) print('hi函數運行完成') names = name_generator() say_hello(names) say_hi(names)
運行效果如下圖所示:

在 say_hello
函數裡面,生成器已經被完整遍歷了一次,那麼在say_hi
裡面,就什麼數據都拿不到了。
但如果我們用的是列表,就可以多次遍歷,如下圖所示:

大家注意觀察區別。
那麼有什麼辦法,能讓生成器被多次完整迭代呢?這個時候就要使用itertools.tee
這個函數了。它通過dequeue
實現了讓生成器多次消費的辦法。
itertools.tee
的使用方法如下:
生成器1, 生成器2, 生成器3 = itertools.tee(原始生成器, 3)
itertools.tee
的第一個參數是原始生成器,第二個參數是你希望讓它返回多少個可以復用的生成器。
例如:
import itertools def name_generator(): for name in ['產品經理', 'kingname']: yield name def say_hello(g): print('hello 函數開始運行') for name in g: print('hello', name) print('hello 函數運行完成') def say_hi(g): print('hi函數開始運行') for name in g: print('hi', name) print('hi函數運行完成') names = name_generator() names_1, names_2 = itertools.tee(names, 2) say_hello(names_1) say_hi(names_2)
運行效果如下圖所示:

但是,itertools.tee
有兩個缺陷:
其一是如果原始生成器能循環非常多次,產生的數據量非常大,並且你在消費的時候,是先迭代第一個分裂後的生成器,完整迭代完以後再迭代第二個分裂後的生成器,那麼這將會浪費大量記憶體。所以,應該讓兩個生成器能間隔著迭代,或者「同時」迭代。
其二,多個生成器同時迭代也有問題,分裂出來的多個生成器不是執行緒安全的,在多執行緒裡面同時運行會導致報錯。
在接下來的兩篇文章中,我會講到itertools.tee
是如何做到讓生成器多次迭代的,然後講到如何讓分裂以後的生成器執行緒安全。