Python 中的迭代器趣味實(shí)踐
上節(jié)課我們學(xué)習(xí)了迭代器的實(shí)現(xiàn)原理,這節(jié)課我們來(lái)動(dòng)手實(shí)踐一下:
1. 遍歷文本文件中的單詞
假設(shè)存在文本文件 test.txt,內(nèi)容如下:
The Zen of Python
Beautiful is better than ugly
Simple is better than complex
注意文件包含有空行,要求完成如下任務(wù):
- 統(tǒng)計(jì)文件有多少個(gè)單詞
- 統(tǒng)計(jì)文件中每個(gè)單詞出現(xiàn)的頻率
2. 直接遍歷的方法
2.1 統(tǒng)計(jì)單詞個(gè)數(shù)
假設(shè)沒有學(xué)習(xí)迭代器,使用直接遍歷的方法實(shí)現(xiàn) “統(tǒng)計(jì)單詞個(gè)數(shù)” 的功能需求,代碼如下:
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 標(biāo)識(shí)已經(jīng)打開的文件
- 在第 2 行,變量 count 用于記錄文件中單詞的個(gè)數(shù)
- 程序邏輯由兩個(gè)循環(huán)構(gòu)成:外循環(huán)和內(nèi)循環(huán)
- 在第 4 行,外循環(huán),遍歷文件的每一行文本
- 在第 5 行,讀取文件的一行
- 在第 6 行,如果 not line 為真,表示讀取到文件的結(jié)束,退出程序
- 在第 10 行,內(nèi)循環(huán),遍歷每一行文本的單詞
- 在第 9 行,使用 split 方法將文本分割為多個(gè)單詞,將結(jié)果保存在列表 words 中
- 在第 10 行,使用 for 循環(huán)遍歷列表 words
- 在第 11 行,打印當(dāng)前遍歷的單詞
- 在第 12 行,統(tǒng)計(jì)單詞個(gè)數(shù)
- 在第 4 行,外循環(huán),遍歷文件的每一行文本
- 在第 13 行,打印單詞的總個(gè)數(shù)
注意,程序能夠?qū)招羞M(jìn)行正確的處理:
- 在第 9 行,使用 split 方法將 line 分割為多個(gè)單詞
- 如果 line 為空行,則 split 返回一個(gè)空列表 []
- 在第 11 行,使用 for 循環(huán)遍歷一個(gè)空列表,不會(huì)執(zhí)行 for 循環(huán)的循環(huán)體代碼
程序運(yùn)行輸出結(jié)果如下:
The
Zen
of
Python
Beautiful
is
better
than
ugly
Simple
is
better
than
complex
count = 14
2.2 統(tǒng)計(jì)單詞出現(xiàn)頻率
假設(shè)沒有學(xué)習(xí)迭代器,使用直接遍歷的方法實(shí)現(xiàn) “統(tǒng)計(jì)單詞出現(xiàn)頻率” 的功能需求,代碼如下:
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 標(biāo)識(shí)已經(jīng)打開的文件
- 在第 2 行,字典 dict 用于記錄文件中單詞的出現(xiàn)頻率
- 字典 dict 的鍵為單詞
- 字典 dict 的值為該單詞在文本中出現(xiàn)的次數(shù)
- 程序邏輯由兩個(gè)循環(huán)構(gòu)成:外循環(huán)和內(nèi)循環(huán)
- 在第 4 行,外循環(huán),遍歷文件的每一行文本
- 在第 5 行,讀取文件的一行
- 在第 6 行,如果 not line 為真,表示讀取到文件的結(jié)束,退出程序
- 在第 10 行,內(nèi)循環(huán),遍歷每一行文本的單詞
- 在第 9 行,使用 split 方法將文本分割為多個(gè)單詞,將結(jié)果保存在列表 words 中
- 在第 11 行,如果 word 已經(jīng)存在于 dict 中
- 則在第 12 行,該單詞出現(xiàn)的次數(shù)加 1
- 在第 13 行,如果 word 不存在于 dict 中
- 則在第 14 行,該單詞出現(xiàn)的次數(shù)初始化為 1
- 在第 4 行,外循環(huán),遍歷文件的每一行文本
- 在第 16 行,打印 dict 的鍵和值
程序運(yùn)行輸出結(jié)果如下:
The: 1
Zen: 1
of: 1
Python: 1
Beautiful: 1
is: 2
better: 2
than: 2
ugly: 1
Simple: 1
complex: 1
結(jié)果表明:
- 單詞 is better than 出現(xiàn)了 2 次
- 其它單詞出現(xiàn)了 1 次
2.3 直接遍歷的方法的問(wèn)題
2.1 小節(jié)程序的框架與 2.2 小節(jié)程序的框架類似:
- 程序的主體結(jié)構(gòu)由兩重循環(huán)構(gòu)成:外循環(huán)和內(nèi)循環(huán)
- 外循環(huán),遍歷文件的每一行文本
- 內(nèi)循環(huán),遍歷每一行文本的單詞
它們的不同之處在于:
- 遍歷每個(gè)單詞時(shí),2.1 小節(jié)的程序執(zhí)行如下代碼統(tǒng)計(jì)單詞個(gè)數(shù)
count = count + 1
- 遍歷每個(gè)單詞時(shí),2.2 小節(jié)的程序執(zhí)行如下代碼統(tǒng)計(jì)單詞出現(xiàn)頻率
if word in dict:
dict[word] += 1
else:
dict[word] = 1
這兩個(gè)小節(jié)的程序的其它代碼則完全一樣,程序中存在明顯的代碼重復(fù)。
3. 使用迭代器的方法
3.1 可迭代對(duì)象與迭代器
本節(jié)實(shí)現(xiàn)類 IterateWord 用于簡(jiǎn)化遍歷文本中的單詞,**類 IterateWord 既是可迭代對(duì)象也是迭代器: **
- 類 IterateWord 是可迭代對(duì)象,提供了 __iter__ 方法,返回一個(gè)迭代器
- 類 IterateWord 是迭代器,提供了 __next__ 方法,返回下一個(gè)遍歷的對(duì)象
類 IterateWord 的定義如下:
class IterateWord:
def __init__(self, file):
self.file = file
self.words = []
- 在第 2 行,參數(shù) file 指明了被遍歷的文本文件
- 在第 3 行,將參數(shù) file 保存到成員變量中
- 在第 4 行,IterateWord 將每一行文本分割為多個(gè)單詞,保存在 self.words 中,該變量初始化為空列表
3.2 實(shí)現(xiàn) __iter__ 方法
類 IterateWord 是一個(gè)可迭代對(duì)象,需要向外界提供 __iter__ 方法,該方法的實(shí)現(xiàn)如下:
def __iter__(self):
return self
類 IterateWord 既是可迭代對(duì)象也是迭代器,返回 self 表示 self 是一個(gè)迭代器。
3.3 實(shí)現(xiàn) __next__ 方法
類 IterateWord 是一個(gè)迭代器,需要向外界提供 __next__ 方法,該方法的實(shí)現(xiàn)如下:
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 中的單詞數(shù)量為 0
- 在第 3 行,調(diào)用 get_non_blank_line 方法讀取一個(gè)非空的行
- 在第 4 行,使用 words.pop(0) 從 words 中刪除第 0 個(gè)單詞,即該行文本的首個(gè)單詞
- 在第 5 行,返回從 words 中刪除的第 0 個(gè)單詞
get_non_blank_line 方法讀取一個(gè)非空的行,代碼如下:
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 行,使用循環(huán)依次讀取文件的每行文本
- 在第 3 行,使用 readline 方法讀取文件的一行文本
- 在第 4 行,not line 為真表示讀取到文件結(jié)束
- 在第 5 行,拋出異常 StopIteration,表示遍歷結(jié)束
- 在第 6 行,將 line 分割為多個(gè)單詞
- 如果 line 是一個(gè)空行,則 len(words) == 0,需要跳過(guò)這種情況,讀取下一行文本
- 如果 line 不是一個(gè)空行,則 len(words) != 0,在第 7 行執(zhí)行 break 退出循環(huán),結(jié)束函數(shù)的執(zhí)行,此時(shí)列表 self.words 中必定包含有若干個(gè)單詞
4. 使用迭代器解決需求
4.1 統(tǒng)計(jì)單詞個(gè)數(shù)
本節(jié)基于前面已經(jīng)實(shí)現(xiàn)的迭代器,完成統(tǒng)計(jì)單詞個(gè)數(shù)的任務(wù),代碼如下:
file = open('test.txt')
count = 0
for word in IterateWord(file):
print(word)
count = count + 1
- 在第 1 行,打開文件 test.txt
- 在第 2 行,變量 count 用于記錄文件中單詞的個(gè)數(shù)
- 在第 4 行,遍歷文件中的每一個(gè)單詞
- 在第 5 行,打印當(dāng)前遍歷的單詞
- 在第 6 行,統(tǒng)計(jì)單詞個(gè)數(shù)
程序運(yùn)行輸出結(jié)果如下:
The
Zen
of
Python
Beautiful
is
better
than
ugly
Simple
is
better
than
complex
count = 14
4.2 統(tǒng)計(jì)單詞出現(xiàn)頻率
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 標(biāo)識(shí)已經(jīng)打開的文件
- 在第 4 行,遍歷每一行文本的單詞
- 在第 5 行,如果 word 已經(jīng)存在于 dict 中
- 則在第 5 行,該單詞出現(xiàn)的次數(shù)加 1
- 在第 7 行,如果 word 不存在于 dict 中
- 則在第 8 行,該單詞出現(xiàn)的次數(shù)初始化為 1
- 在第 5 行,如果 word 已經(jīng)存在于 dict 中
- 在第 10 行,打印 dict 的鍵和值
程序運(yùn)行輸出結(jié)果如下:
The: 1
Zen: 1
of: 1
Python: 1
Beautiful: 1
is: 2
better: 2
than: 2
ugly: 1
Simple: 1
complex: 1
結(jié)果表明:
- 單詞 is better than 出現(xiàn)了 2 次
- 其它單詞出現(xiàn)了 1 次
4.3 總結(jié)
4.3.1 簡(jiǎn)化了遍歷的代碼
基于迭代器的方法解決 “統(tǒng)計(jì)單詞個(gè)數(shù)” 與 “統(tǒng)計(jì)單詞出現(xiàn)頻率” 這兩個(gè)任務(wù),遍歷文本中的單詞的代碼非常簡(jiǎn)潔,如下所示:
for word in IterateWord(file):
處理 word
IterateWord 屏蔽了文件由多行構(gòu)成、可能存在空行、每行由多個(gè)單詞構(gòu)成等細(xì)節(jié),遍歷文件中的單詞非常的方便。
4.3.2 迭代器的實(shí)現(xiàn)復(fù)雜
直接遍歷文件單詞的代碼如下:
while True:
line = file.readline()
if not line:
break
words = line.split()
for word in words:
處理 word
使用直接遍歷文件單詞的方式解決 “統(tǒng)計(jì)單詞個(gè)數(shù)” 與 “統(tǒng)計(jì)單詞出現(xiàn)頻率” 這兩個(gè)任務(wù),存在有明顯的代碼重復(fù)。雖然代碼重復(fù),但是代碼很直觀、容易理解。
與之相比,IterateWord 的實(shí)現(xiàn)較為復(fù)雜、不夠直觀,Python 中提供了生成器的語(yǔ)法,可以用于簡(jiǎn)化迭代器的實(shí)現(xiàn)。請(qǐng)查找詞條 “Python 中的生成器實(shí)現(xiàn)原理” 和 “Python 中的迭代器趣味實(shí)踐”,閱讀如何使用生成器簡(jiǎn)化實(shí)現(xiàn)迭代器。