標籤:

第十四章 可迭代的對象、迭代器和生成器

迭代是數據處理的基礎,當掃描內存中存放不下的數據集時,我們需要一種惰性獲取數據項的方式,即按需求一次獲取一個數據項。這就是迭代器模式。

在Python中,所有集合都可以迭代,在Python語言的內部、迭代器用於支持:

  • for 循環
  • 構建和擴展集合類型
  • 逐行遍歷文本文件
  • 列表推導、字典推導和集合推導
  • 元組拆包
  • 調用函數時,使用*號拆包參數

首先,我們來研究 iter(...)如何讓序列變的可以迭代

單詞序列v1

首先我們來嘗試構造一個單詞序列類型,並檢測其是否支持迭代

import renimport reprlibnnRE_WORD = re.compile(w+)nnclass Sentence:nn def __init__(self, text):n self.text = textn # 返回一個字元串列表、元素為正則所匹配到的非重疊匹配】n self.words = RE_WORD.findall(text)nn def __getitem__(self, index):n # 返回指定索引上的單詞n return self.words[index]nn def __len__(self):n # 完善序列的協議,我們實現len方法n return len(self.words)nn def __repr__(self):n # 該函數用於生成大型數據結構的簡略字元串的表現形式n return Sentence(%s) % reprlib.repr(self.text)nnn# TEST 該類是否能夠完成迭代nns = Sentence(Ehco is a good Python coder)nprint(s)nfor word in s:n print(word)nnnOUT:nnSentence(Ehco is a good Python coder) # 該字元串是有repr方法生成的nEhco # 可以看到,我們的Sentence類已經支持迭代輸出了nisnangoodnPythonncodernn

該序列可以迭代的原因

首先我們現了__len__ 方法,這樣遵守了Python序列的協議

這樣可以讓Python的解釋器明白,該類是序列類型

當解釋器需要迭代對象X時,會自動調用 iter(x)函數

該內置函數有一下作用:

  • 檢測對象是否實現了__iter__方法如果實現了,就調用獲得一個迭代器
  • 如果沒有實現該方法,嘗試調用__getitem__方法,Python會自動創建一個迭代器從序列的[0]位置開始獲取元素
  • 如果都失敗了,就會返回不可迭代的錯誤

任何Python序列都可以迭代的原因正式因為她們都實現了__getitem__方法。

但更好的方法是實現__iter__方法,內置的標準序列都是這麼做的。

那麼我們來實現單詞序列v2

單詞序列V2

在動手之前,我們必須明白迭代器和可迭代對象的關係

簡單的說:Python從可迭代的對象中獲取迭代器

舉個例子,字元串ABC就是一個可迭代的對象,

當我們通過for循環遍歷迭代出元素時,其背後就用我們看不見的迭代器在發揮作用

import renimport reprlibnnRE_WORD = re.compile(w+)nnnclass Sentence:nn def __init__(self, text):n self.text = textn # 返回一個字元串列表、元素為正則所匹配到的非重疊匹配】n self.words = RE_WORD.findall(text)nn def __repr__(self):n # 該函數用於生成大型數據結構的簡略字元串的表現形式n return Sentence(%s) % reprlib.repr(self.text)nn def __iter__(self):n 明確表明該類型是可以迭代的n # 初始化對應的迭代器,並返回n return SentenceIterator(self.words)nnnclass SentenceIterator:nn def __init__(self, words):n self.words = words # 該迭代器實例應用單詞列表n self.index = 0 # 用於定位下一個元素nn def __next__(self):n try:n word = self.words[self.index] # 返回當前的元素n except IndexError:n raise StopIteration()n self.index += 1 # 索引+1n return word # 返回單詞nn def __iter__(self):n return self n

V2版本相對於V1來說,只是添加了__iter__方法,

並實現了相對於的迭代器

迭代器本身我們也實現了__iter__方法,用於返回迭代器本身

那麼,迭代器可以迭代,但可迭代的對象不是迭代器!

單詞序列v3

V2版本已經構造了一個可以迭代的單詞序列,

但更加符合Python風格的是用生成器函數替代迭代器

import renimport reprlibnnRE_WORD = re.compile(w+)nnnclass Sentence:nn def __init__(self, text):n self.text = textn # 返回一個字元串列表、元素為正則所匹配到的非重疊匹配】n self.words = RE_WORD.findall(text)nn def __repr__(self):n # 該函數用於生成大型數據結構的簡略字元串的表現形式n return Sentence(%s) % reprlib.repr(self.text)nn def __iter__(self):n 生成器版本n for word in self.words: # 迭代實例的wordsn yield word # 生成單詞n return # 這個return不是必要的,因為該韓式可以自動返回(在生成完全部的值之後)n

當我們使用生成器之後,是不是覺得代碼更具有Python的風格了呢?

生成器函數的工作原理

只要Python函數的定義體重有關鍵字yield,該函數就是生成器函數,調用生成器函數時,會返回一個生成器對象,也就是說,生成器函數是生成器工廠。

這樣說可能不明白,我們來舉個例子

def gen_AB():n print (start)n yield An print(continue)n yield Bn print(end)nnnfor c in gen_AB():n print(c)nnnOut:nnstartnAncontinuenBnendnn

我們使用for循環來說明生成器函數定義體執行的過程:

  • for 循環中第一次隱式調用next(),會列印出start,然後停在第一個yield語句,生成值A
  • for 循環中第二次隱式調用next(),會列印出continue,然後停在第一個yield語句,生成值B
  • 最後會列印end,並拋出StopIteration異常,停止整個動作。

單詞序列V4

雖然看上去我們使用了生成器每次返回一個元素

但是實際上實例在初始化的時候,已經迫切的構建好了文本中的單詞列表

這樣會導致我們需要的內存和文本本身一樣甚至更多,這樣的方法現的過於急切了

我們如合來構建一個惰性的版本?

re.findier 是re.findall 的惰性版本,他返回的不是列表

而是一個生成器,這樣可以大大節省內存

import renimport reprlibnnRE_WORD = re.compile(w+)nnnclass Sentence:nn def __init__(self, text):n self.text = textn # 返回一個字元串列表、元素為正則所匹配到的非重疊匹配】n self.words = RE_WORD.findall(text)nn def __repr__(self):n # 該函數用於生成大型數據結構的簡略字元串的表現形式n return Sentence(%s) % reprlib.repr(self.text)nn def __iter__(self):n 惰性生成器版本n for match in RE_WORD.finditer(self.text):n yield match.group()n

單詞序列v5

當行為比較簡單時,

我們可以用更簡潔的生成器表達式來替代生成

兩者在本質上是一樣的,後者在代碼的書寫上更加簡潔

import renimport reprlibnnRE_WORD = re.compile(w+)nnnclass Sentence:nn def __init__(self, text):n self.text = textn # 返回一個字元串列表、元素為正則所匹配到的非重疊匹配】n self.words = RE_WORD.findall(text)nn def __repr__(self):n # 該函數用於生成大型數據結構的簡略字元串的表現形式n return Sentence(%s) % reprlib.repr(self.text)nn def __iter__(self):n 惰性生成器表達式版本n return (match.group() for match in RE_WORD.finditer(self.text))n

本章小結

在這個章節中,作者帶領我們不斷深入了解Python迭代器/生成器相關的用法

並動手實現了單詞序列類的編寫,

隨著不停的重構,代碼越來越符合Python的風格

整體也越來越簡短,最終,我們明白了生成器工作的原理

本章還有一些其他的內容:

如標準庫中的生成器函數等

有興趣的小夥伴可以自己去閱讀一下原文。

每天的學習記錄都會 同步更新到:

微信公眾號: findyourownway

知乎專欄:zhuanlan.zhihu.com/zen-

Blog : www.ehcoblog.ml

Github: github.com/Ehco1996/


推薦閱讀:

【問題】pyspider安裝過程的一些問題
將Python2中漢字出現編碼的事一次性說清楚。
關於 Live 的預告

TAG:Python |