一日一技:一個生成器如何當兩個用?

  • 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是如何做到讓生成器多次迭代的,然後講到如何讓分裂以後的生成器執行緒安全。