亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Python 中的迭代器趣味實踐

上節課我們學習了迭代器的實現原理,這節課我們來動手實踐一下:

1. 遍歷文本文件中的單詞

假設存在文本文件 test.txt,內容如下:

The Zen of Python

Beautiful is better than ugly

Simple is better than complex

注意文件包含有空行,要求完成如下任務:

  1. 統計文件有多少個單詞
  2. 統計文件中每個單詞出現的頻率

2. 直接遍歷的方法

2.1 統計單詞個數

假設沒有學習迭代器,使用直接遍歷的方法實現 “統計單詞個數” 的功能需求,代碼如下:

file = open('test.txt')
count = 0

while True:
    line = file.readline()
    if not line:
        break

    words = line.split() 
    for word in words:
        print(word)
        count = count + 1
print('count = %d' % count)
  • 在第 1 行,打開文件 test.txt,變量 file 標識已經打開的文件
  • 在第 2 行,變量 count 用于記錄文件中單詞的個數
  • 程序邏輯由兩個循環構成:外循環和內循環
    • 在第 4 行,外循環,遍歷文件的每一行文本
      • 在第 5 行,讀取文件的一行
      • 在第 6 行,如果 not line 為真,表示讀取到文件的結束,退出程序
    • 在第 10 行,內循環,遍歷每一行文本的單詞
      • 在第 9 行,使用 split 方法將文本分割為多個單詞,將結果保存在列表 words 中
      • 在第 10 行,使用 for 循環遍歷列表 words
      • 在第 11 行,打印當前遍歷的單詞
      • 在第 12 行,統計單詞個數
  • 在第 13 行,打印單詞的總個數

注意,程序能夠對空行進行正確的處理:

  • 在第 9 行,使用 split 方法將 line 分割為多個單詞
  • 如果 line 為空行,則 split 返回一個空列表 []
  • 在第 11 行,使用 for 循環遍歷一個空列表,不會執行 for 循環的循環體代碼

程序運行輸出結果如下:

The
Zen
of
Python
Beautiful
is
better
than
ugly
Simple
is
better
than
complex
count = 14

2.2 統計單詞出現頻率

假設沒有學習迭代器,使用直接遍歷的方法實現 “統計單詞出現頻率” 的功能需求,代碼如下:

file = open('test.txt')
dict = {}

while True:
    line = file.readline()
    if not line:
        break

    words = line.split() 
    for word in words:
        if word in dict:
            dict[word] += 1
        else:
            dict[word] = 1

for word,count in dict.items():
    print('%s: %d' % (word, count))            
  • 在第 1 行,打開文件 test.txt,變量 file 標識已經打開的文件
  • 在第 2 行,字典 dict 用于記錄文件中單詞的出現頻率
    • 字典 dict 的鍵為單詞
    • 字典 dict 的值為該單詞在文本中出現的次數
  • 程序邏輯由兩個循環構成:外循環和內循環
    • 在第 4 行,外循環,遍歷文件的每一行文本
      • 在第 5 行,讀取文件的一行
      • 在第 6 行,如果 not line 為真,表示讀取到文件的結束,退出程序
    • 在第 10 行,內循環,遍歷每一行文本的單詞
      • 在第 9 行,使用 split 方法將文本分割為多個單詞,將結果保存在列表 words 中
      • 在第 11 行,如果 word 已經存在于 dict 中
        • 則在第 12 行,該單詞出現的次數加 1
      • 在第 13 行,如果 word 不存在于 dict 中
        • 則在第 14 行,該單詞出現的次數初始化為 1
  • 在第 16 行,打印 dict 的鍵和值

程序運行輸出結果如下:

The: 1
Zen: 1
of: 1
Python: 1
Beautiful: 1
is: 2
better: 2
than: 2
ugly: 1
Simple: 1
complex: 1

結果表明:

  • 單詞 is better than 出現了 2 次
  • 其它單詞出現了 1 次

2.3 直接遍歷的方法的問題

2.1 小節程序的框架與 2.2 小節程序的框架類似:

  • 程序的主體結構由兩重循環構成:外循環和內循環
  • 外循環,遍歷文件的每一行文本
  • 內循環,遍歷每一行文本的單詞

它們的不同之處在于:

  • 遍歷每個單詞時,2.1 小節的程序執行如下代碼統計單詞個數
count = count + 1
  • 遍歷每個單詞時,2.2 小節的程序執行如下代碼統計單詞出現頻率
if word in dict:
    dict[word] += 1
else:
    dict[word] = 1

這兩個小節的程序的其它代碼則完全一樣,程序中存在明顯的代碼重復。

3. 使用迭代器的方法

3.1 可迭代對象與迭代器

本節實現類 IterateWord 用于簡化遍歷文本中的單詞,**類 IterateWord 既是可迭代對象也是迭代器: **

  • 類 IterateWord 是可迭代對象,提供了 __iter__ 方法,返回一個迭代器
  • 類 IterateWord 是迭代器,提供了 __next__ 方法,返回下一個遍歷的對象

類 IterateWord 的定義如下:

class IterateWord:
    def __init__(self, file):
        self.file = file
        self.words = []
  • 在第 2 行,參數 file 指明了被遍歷的文本文件
  • 在第 3 行,將參數 file 保存到成員變量中
  • 在第 4 行,IterateWord 將每一行文本分割為多個單詞,保存在 self.words 中,該變量初始化為空列表

3.2 實現 __iter__ 方法

類 IterateWord 是一個可迭代對象,需要向外界提供 __iter__ 方法,該方法的實現如下:

    def __iter__(self):
        return self

類 IterateWord 既是可迭代對象也是迭代器,返回 self 表示 self 是一個迭代器。

3.3 實現 __next__ 方法

類 IterateWord 是一個迭代器,需要向外界提供 __next__ 方法,該方法的實現如下:

    def __next__(self):
        if len(self.words) == 0:
            self.get_non_blank_line()
        word = self.words.pop(0)
        return word
  • 在第 1 行,定義 __next__ 方法
  • IterateWord 讀取一行文本后,將該文本分割為單詞列表,保存在 words 中
    • 在第 2 行,如果列表 words 中的單詞數量為 0
    • 在第 3 行,調用 get_non_blank_line 方法讀取一個非空的行
  • 在第 4 行,使用 words.pop(0) 從 words 中刪除第 0 個單詞,即該行文本的首個單詞
  • 在第 5 行,返回從 words 中刪除的第 0 個單詞

get_non_blank_line 方法讀取一個非空的行,代碼如下:

    def get_non_blank_line(self):
        while True:
            line = file.readline()
            if not line:
                raise StopIteration
            self.words = line.split() 
            if len(self.words) != 0:
                break
  • 在第 2 行,使用循環依次讀取文件的每行文本
  • 在第 3 行,使用 readline 方法讀取文件的一行文本
  • 在第 4 行,not line 為真表示讀取到文件結束
    • 在第 5 行,拋出異常 StopIteration,表示遍歷結束
  • 在第 6 行,將 line 分割為多個單詞
    • 如果 line 是一個空行,則 len(words) == 0,需要跳過這種情況,讀取下一行文本
    • 如果 line 不是一個空行,則 len(words) != 0,在第 7 行執行 break 退出循環,結束函數的執行,此時列表 self.words 中必定包含有若干個單詞

4. 使用迭代器解決需求

4.1 統計單詞個數

本節基于前面已經實現的迭代器,完成統計單詞個數的任務,代碼如下:

file = open('test.txt')
count = 0

for word in IterateWord(file):
    print(word)
    count = count + 1
  • 在第 1 行,打開文件 test.txt
  • 在第 2 行,變量 count 用于記錄文件中單詞的個數
  • 在第 4 行,遍歷文件中的每一個單詞
    • 在第 5 行,打印當前遍歷的單詞
    • 在第 6 行,統計單詞個數

程序運行輸出結果如下:

The
Zen
of
Python
Beautiful
is
better
than
ugly
Simple
is
better
than
complex
count = 14

4.2 統計單詞出現頻率

file = open('test.txt')
dict = {}

for word in IterateWord(file):
    if word in dict:
        dict[word] += 1
    else:
        dict[word] = 1

for word,count in dict.items():
    print('%s: %d' % (word, count))          
  • 在第 1 行,打開文件 test.txt,變量 file 標識已經打開的文件
  • 在第 4 行,遍歷每一行文本的單詞
    • 在第 5 行,如果 word 已經存在于 dict 中
      • 則在第 5 行,該單詞出現的次數加 1
    • 在第 7 行,如果 word 不存在于 dict 中
      • 則在第 8 行,該單詞出現的次數初始化為 1
  • 在第 10 行,打印 dict 的鍵和值

程序運行輸出結果如下:

The: 1
Zen: 1
of: 1
Python: 1
Beautiful: 1
is: 2
better: 2
than: 2
ugly: 1
Simple: 1
complex: 1

結果表明:

  • 單詞 is better than 出現了 2 次
  • 其它單詞出現了 1 次

4.3 總結

4.3.1 簡化了遍歷的代碼

基于迭代器的方法解決 “統計單詞個數” 與 “統計單詞出現頻率” 這兩個任務,遍歷文本中的單詞的代碼非常簡潔,如下所示:

for word in IterateWord(file):
    處理 word

IterateWord 屏蔽了文件由多行構成、可能存在空行、每行由多個單詞構成等細節,遍歷文件中的單詞非常的方便。

4.3.2 迭代器的實現復雜

直接遍歷文件單詞的代碼如下:

while True:
    line = file.readline()
    if not line:
        break

    words = line.split() 
    for word in words:
        處理 word

使用直接遍歷文件單詞的方式解決 “統計單詞個數” 與 “統計單詞出現頻率” 這兩個任務,存在有明顯的代碼重復。雖然代碼重復,但是代碼很直觀、容易理解

與之相比,IterateWord 的實現較為復雜、不夠直觀,Python 中提供了生成器的語法,可以用于簡化迭代器的實現。請查找詞條 “Python 中的生成器實現原理” 和 “Python 中的迭代器趣味實踐”,閱讀如何使用生成器簡化實現迭代器。