翻譯:《實用的Python編程》06_02_Customizing_iteration

目錄 | 上一節 (6.1 迭代協議) | 下一節 (6.3 生產者/消費者)html

6.2 自定義迭代

本節探究如何使用生成器函數自定義迭代。python

問題

假設你想要自定義迭代模式。git

例如:倒數:github

>>> for x in countdown(10):
...   print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>

有一個j簡單方法能夠作到這一點。bash

生成器

生成器(generator)是定義了迭代的函數:服務器

def countdown(n):
    while n > 0:
        yield n
        n -= 1

示例:函數

>>> for x in countdown(10):
...   print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>

任何使用了 yield 語句的函數稱爲生成器。命令行

生成器函數的行爲不一樣於普通於普通函數。調用生成器函數會建立一個生成器對象(generator object),而不是當即執行函數:翻譯

def countdown(n):
    # Added a print statement
    print('Counting down from', n)
    while n > 0:
        yield n
        n -= 1
>>> x = countdown(10)
# There is NO PRINT STATEMENT
>>> x
# x is a generator object
<generator object at 0x58490>
>>>

生成器函數只在 __next__() 方法被調用時才執行:調試

>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>

yield 生成一個值,可是掛起(suspend)函數執行。生成器函數會在下次調用 __next__() 方法時恢復(resume),

>>> x.__next__()
9
>>> x.__next__()
8

當生成器返回最後一個值後,再次迭代將會觸發一個錯誤(譯註:StopIteration)。

>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in ? StopIteration
>>>

觀察:生成器函數實現的協議與 for 語句在列表、元組、字典、文件上使用的底層協議相同。

練習

練習 6.4:一個簡單的生成器

若是想要自定義迭代,那麼你應該始終考慮生成器函數。生成器函數很是容易編寫——建立一個函數,執行所需的迭代邏輯,並使用 yield 發送一個值。

例如,建立一個在文件各行中查找匹配子串的生成器:

>>> def filematch(filename, substr):
        with open(filename, 'r') as f:
            for line in f:
                if substr in line:
                    yield line

>>> for line in open('Data/portfolio.csv'):
        print(line, end='')

name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>> for line in filematch('Data/portfolio.csv', 'IBM'):
        print(line, end='')

"IBM",50,91.10
"IBM",100,70.44
>>>

這是一種有趣的思想——你能夠在函數中隱藏自定義的處理過程,並將該函數應用於 for 循環。下一個例子探究一種更不尋常的狀況。

練習 6.5:監視流數據源

生成器可應用於監視實時數據源(如:日誌文件,股票市場消息)。本部分,咱們將對「使用生成器監視實時數據源」這一思想進行探索。首先,請嚴格遵循如下說明。

Data/stocksim.py 用來模仿股市數據,將實時數據不斷地寫入到 Data/stocklog.csv 文件。請打開一個獨立的命令行窗口,進入到 Data/ 目錄,而後運行 stocksim.py 程序:

bash % python3 stocksim.py

若是你使用的是 Windows 系統,那麼請找到 stocksim.py 文件,而後雙擊該文件運行。而後,讓咱們先把這個程序放到一邊(讓它一直在那運行),打開另一個命令行窗口,查看正在被模擬程序(譯註:stocksim.py)寫入數據的 Data/stocklog.csv 文件(譯註:若是使用的是 Linux 系統,那麼能夠進入到 Data 目錄下,而後使用 tail -f stocklog.csv 命令查看)。你應該能夠看到每隔幾秒新的文本行被添加到 Data/stocklog.csv 文件中。一樣,讓程序在後臺運行——該程序會運行幾個小時(對此不用擔憂)。

stocksim.py 程序運行後,讓咱們編寫一個程序來打開 Data/stocklog.csv 文件、移動到文件末尾、並查看新的輸出。請在 Work 目錄下建立 follow.py 文件,並把如下代碼放入其中:

# follow.py
import os
import time

f = open('Data/stocklog.csv')
f.seek(0, os.SEEK_END)   # Move file pointer 0 bytes from end of file

while True:
    line = f.readline()
    if line == '':
        time.sleep(0.1)   # Sleep briefly and retry
        continue
    fields = line.split(',')
    name = fields[0].strip('"')
    price = float(fields[1])
    change = float(fields[4])
    if change < 0:
        print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

運行 follow.py 程序,你將會看到實時的股票報價(stock ticker)。 follow.py 裏的代碼相似於 Unix 系統查看日誌文件的 tail -f 命令。

注意事項:在本示例中,readline() 方法的使用與一般從文件中讀取行的方式稍微有點不一樣(一般使用 for 循環)。在這種狀況下,咱們使用 readline() 來重複探測文件的末尾,以查看是否添加了新的數據(readline() 方法返回新的數據或者空字符串)。

練習 6.6:使用生成器生成數據

查看練習 6.5 中代碼你會發現,代碼的第一部分產生了幾行數據,而 while 循環末尾的語句消費數據。生成器的一個主要特性是你能夠將生成數據的代碼移動到可重用的函數中。

請修改練習 6.5 的代碼,以便經過生成器函數 follow(filename) 執行文件讀取。請實現更改以便下面的代碼可以工做:

>>> for line in follow('Data/stocklog.csv'):
          print(line, end='')

... Should see lines of output produced here ...

請修改股票報價代碼,使代碼看起來像下面這樣:

if __name__ == '__main__':
    for line in follow('Data/stocklog.csv'):
        fields = line.split(',')
        name = fields[0].strip('"')
        price = float(fields[1])
        change = float(fields[4])
        if change < 0:
            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

練習 6.7:查看股票投資組合

請修改 follow.py 程序,以便程序可以查看股票數據流,並打印股票投資組合中的那些股票的信息。示例:

if __name__ == '__main__':
    import report

    portfolio = report.read_portfolio('Data/portfolio.csv')

    for line in follow('Data/stocklog.csv'):
        fields = line.split(',')
        name = fields[0].strip('"')
        price = float(fields[1])
        change = float(fields[4])
        if name in portfolio:
            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')

注意事項:要想這段代碼可以運行, Portfolio 類必須支持 in 運算符。請參閱 練習 6.3 ,確保 Portfolio 類實現了 __contains__() 運算符。

討論

在這裏,你將一個有趣的迭代模式(在文件末尾讀取行)移動到函數中。follow()函數如今是能夠在任何程序中使用的徹底通用的實用程序。例如,你可使用 follow() 函數查看服務器日誌、調試日誌、其它相似的數據源。

目錄 | 上一節 (6.1 迭代協議) | 下一節 (6.3 生產者/消費者)

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索