目錄 | 上一節 (6.2 自定義迭代) | 下一節 (6.4 生成器表示式)
6.3 生產者,消費者和管道
生成器在設定各種生產者/消費者問題(producer/consumer problems)和資料流管道(pipeline)中非常有用。本節將對此進行討論。
生產者消費者問題
生成器與各種形式的 生產者消費者 問題密切相關。
# Producer
def follow(f):
...
while True:
...
yield line # Produces value in `line` below
...
# Consumer
for line in follow(f): # Consumes value from `yield` above
...
yield
語句生成給 for
語句消費的值。
生成器管道
你可以使用生成器的這方面特性來設定程式管道(類似於 Unix 管道(pipe))。
producer → processing → processing → consumer
程式管道包括初始的資料生產者、中間的處理階段、最後的消費者。
producer → processing → processing → consumer
def producer():
...
yield item
...
通常情況下,生產者是一個生成器,儘管也可以是其它的序列列表。yield
將資料輸入管道。
producer → processing → processing → consumer
def consumer(s):
for item in s:
...
消費者是一個 for 迴圈,獲取資料(譯註:items)並對資料執行某些操作。
producer → processing → processing → consumer
def processing(s):
for item in s:
...
yield newitem
...
中間的處理階段同時消費和生產資料。它們可能修改資料流,也可能篩選資料流(丟棄資料)。
producer → processing → processing → consumer
def producer():
...
yield item # yields the item that is received by the `processing`
...
def processing(s):
for item in s: # Comes from the `producer`
...
yield newitem # yields a new item
...
def consumer(s):
for item in s: # Comes from the `processing`
...
設定管道的程式碼如下:
a = producer()
b = processing(a)
c = consumer(b)
你會發現資料逐漸地流向不同的函式。
練習
對於本練習,stocksim.py
程式仍需要在後臺執行。並且,你將使用到上一節練習(譯註:練習 6.7)編寫的 follow()
函式。
練習 6.8:建立一個簡單的管道
讓我們來看看管道的思想。請建立下面這個函式:
>>> def filematch(lines, substr):
for line in lines:
if substr in line:
yield line
>>>
filematch()
函式除了不再開啟檔案,幾乎與上一節練習的第一個生成器示例完全相同——僅僅對作為引數給出的行序列進行操作。現在,請嘗試如下操作:
>>> from follow import follow
>>> lines = follow('Data/stocklog.csv')
>>> ibm = filematch(lines, 'IBM')
>>> for line in ibm:
print(line)
... wait for output ...
雖然輸出可能需要一定時間才會出現,但是,最後你一定會看到包含 IBM 資料的行。
練習 6.9:建立一個複雜的管道
通過執行更多操作來進一步理解管道的思想。
>>> from follow import follow
>>> import csv
>>> lines = follow('Data/stocklog.csv')
>>> rows = csv.reader(lines)
>>> for row in rows:
print(row)
['BA', '98.35', '6/11/2007', '09:41.07', '0.16', '98.25', '98.35', '98.31', '158148']
['AA', '39.63', '6/11/2007', '09:41.07', '-0.03', '39.67', '39.63', '39.31', '270224']
['XOM', '82.45', '6/11/2007', '09:41.07', '-0.23', '82.68', '82.64', '82.41', '748062']
['PG', '62.95', '6/11/2007', '09:41.08', '-0.12', '62.80', '62.97', '62.61', '454327']
...
這非常有趣。你在這裡可以看到, follow()
函式的輸出被傳遞到 csv.reader()
函式,並且,我們現在得到了一系列拆分的行。
練習 6.10:建立更多管道元件
讓我們把這樣的思想擴充套件到更大的管道中。首先,建立 ticker.py
檔案,然後在 ticker.py
檔案裡面建立一個函式,像上面一樣讀取 CSV 檔案:
# ticker.py
from follow import follow
import csv
def parse_stock_data(lines):
rows = csv.reader(lines)
return rows
if __name__ == '__main__':
lines = follow('Data/stocklog.csv')
rows = parse_stock_data(lines)
for row in rows:
print(row)
接著,建立一個選擇特定列的新函式:
# ticker.py
...
def select_columns(rows, indices):
for row in rows:
yield [row[index] for index in indices]
...
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
return rows
再次執行程式,你應該可以看到輸出縮小如下:
['BA', '98.35', '0.16']
['AA', '39.63', '-0.03']
['XOM', '82.45','-0.23']
['PG', '62.95', '-0.12']
...
再接著,建立一個生成器函式以轉換資料型別並構建字典。示例:
# ticker.py
...
def convert_types(rows, types):
for row in rows:
yield [func(val) for func, val in zip(types, row)]
def make_dicts(rows, headers):
for row in rows:
yield dict(zip(headers, row))
...
def parse_stock_data(lines):
rows = csv.reader(lines)
rows = select_columns(rows, [0, 1, 4])
rows = convert_types(rows, [str, float, float])
rows = make_dicts(rows, ['name', 'price', 'change'])
return rows
...
再次執行程式,你應該能夠看到像下面這樣的字典流:
{ 'name':'BA', 'price':98.35, 'change':0.16 }
{ 'name':'AA', 'price':39.63, 'change':-0.03 }
{ 'name':'XOM', 'price':82.45, 'change': -0.23 }
{ 'name':'PG', 'price':62.95, 'change':-0.12 }
...
練習 6.11:篩選資料
建立一個篩選資料的函式。示例:
# ticker.py
...
def filter_symbols(rows, names):
for row in rows:
if row['name'] in names:
yield row
使用該函式可以篩選出投資組合中的股票:
import report
portfolio = report.read_portfolio('Data/portfolio.csv')
rows = parse_stock_data(follow('Data/stocklog.csv'))
rows = filter_symbols(rows, portfolio)
for row in rows:
print(row)
練習 6.12:整合所有的程式碼
請在 ticker.py
檔案中編寫函式 ticker(portfile, logfile, fmt)
,該函式根據給定的投資組合、日誌檔案和表格格式建立實時的股票報價器。示例:
>>> from ticker import ticker
>>> ticker('Data/portfolio.csv', 'Data/stocklog.csv', 'txt')
Name Price Change
---------- ---------- ----------
GE 37.14 -0.18
MSFT 29.96 -0.09
CAT 78.03 -0.49
AA 39.34 -0.32
...
>>> ticker('Data/portfolio.csv', 'Data/stocklog.csv', 'csv')
Name,Price,Change
IBM,102.79,-0.28
CAT,78.04,-0.48
AA,39.35,-0.31
CAT,78.05,-0.47
...
討論
心得體會:你可以建立各種生成器函式,並把它們連結在一起執行涉及資料流的管道處理。另外,你可以建立一個函式,把一系列的管道階段打包到一個單獨的函式中呼叫(例如 parse_stock_data()
函式)。