摘要:可迭代的對象迭代器和生成器理念迭代是數(shù)據(jù)處理的基石??傻膶ο笈c迭代器的對比從可迭代的對象中獲取迭代器標準的迭代器接口有兩個方法。此外,也沒有辦法還原迭代器。最終,函數(shù)的定義體返回時,外層的生成器對象會拋出異常這一點與迭代器協(xié)議一致。
可迭代的對象、迭代器和生成器 理念
迭代是數(shù)據(jù)處理的基石。掃描內(nèi)存中放不下的數(shù)據(jù)集時,我們要找到一種惰性獲取數(shù)據(jù)
項的方式,即按需一次獲取一個數(shù)據(jù)項。這就是迭代器模式(Iterator pattern)。
看個例子
import re import reprlib RE_WORD = re.compile("w+") class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __getitem__(self, index): return self.words[index] def __len__(self): return len(self.words) def __repr__(self): return "Sentence(%s)" % reprlib.repr(self.text) s = Sentence(""The time has come," the Walrus said,") print(s) for world in s: print(world)
解釋器需要迭代對象 x 時,會自動調(diào)用 iter(x)。
內(nèi)置的 iter 函數(shù)有以下作用。
(1) 檢查對象是否實現(xiàn)了 iter 方法,如果實現(xiàn)了就調(diào)用它,獲取一個迭代器。
(2) 如果沒有實現(xiàn) iter 方法,但是實現(xiàn)了 getitem 方法,Python 會創(chuàng)建一個迭代器,嘗試按順序(從索引 0 開始)獲取元素。
(3) 如果嘗試失敗,Python 拋出 TypeError 異常,通常會提示“C object is not iterable”(C對象不可迭代),其中 C 是目標對象所屬的類。
可迭代的對象與迭代器的對比Python 從可迭代的對象中獲取迭代器
標準的迭代器接口有兩個方法。next
返回下一個可用的元素,如果沒有元素了,拋出 StopIteration 異常。
iter
返回 self,以便在應該使用可迭代對象的地方使用迭代器,例如在 for 循環(huán)中。
abc.Iterator 類
from abc import abstractmethod class Iterator(Iterable): __slots__ = () @abstractmethod def __next__(self): "Return the next item from the iterator. When exhausted, raise StopIteration" raise StopIteration def __iter__(self): return self @classmethod def __subclasshook__(cls, C): if cls is Iterator: if (any("__next__" in B.__dict__ for B in C.__mro__) and any("__iter__" in B.__dict__ for B in C.__mro__)): return True return NotImplemented如何使用 next(...) 函數(shù)使用迭代器
import re import reprlib RE_WORD = re.compile("w+") class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __getitem__(self, index): return self.words[index] def __len__(self): return len(self.words) def __repr__(self): return "Sentence(%s)" % reprlib.repr(self.text) # s = Sentence(""The time has come," the Walrus said,") # print(s) # # for world in s: # print(world) ## it就是構(gòu)造后的迭代器 ## iter(s3) 就是構(gòu)建迭代器的可迭代對象。 s3 = Sentence("Pig and Pepper") it = iter(s3) print(next(it)) print(next(it)) print(next(it)) # 報錯 StopIteration # print(next(it)) print(list(it)) print(list(iter(s3))) print(list(iter(s3)))
因為迭代器只需 next 和 iter 兩個方法,所以除了調(diào)用 next() 方法,以及捕獲 StopIteration 異常之外,沒有辦法檢查是否還有遺留的元素。
此外,也沒有辦法“還原”迭代器。
如果想再次迭代,那就要調(diào)用 iter(...),傳入之前構(gòu)建迭代器的可迭代對象。
傳入迭代器本身沒用,因為前面說過 Iterator.__iter__ 方法的實現(xiàn)方式是
返回實例本身,所以傳入迭代器無法還原已經(jīng)耗盡的迭代器。
import re import reprlib RE_WORD = re.compile("w+") class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __repr__(self): return "Sentence(%s)" % reprlib.repr(self.text) def __iter__(self): return SentenceIterator(self.words) class SentenceIterator: def __init__(self, words): self.words = words self.index = 0 def __next__(self): try: word = self.words[self.index] except IndexError: raise StopIteration() self.index += 1 return word def __iter__(self): return self s = Sentence(""The time has come," the Walrus said,") for word in s: print(word)
? 與前一版相比,這里只多了一個 iter 方法。這一版沒有 getitem 方法,為
的是明確表明這個類可以迭代,因為實現(xiàn)了 iter 方法。
? 根據(jù)可迭代協(xié)議,__iter__ 方法實例化并返回一個迭代器。
Sentence 類中,__iter__ 方法調(diào)用 SentenceIterator 類的構(gòu)造方法創(chuàng)建一個迭代器并將其返回。為什么 不寫在一起
把Sentence變成迭代器:壞主意
構(gòu)建可迭代的對象和迭代器時經(jīng)常會出現(xiàn)錯誤,原因是混淆了二者。
要知道,可迭代的對象有個 iter 方法,每次都實例化一個新的迭代器;
而迭代器要實現(xiàn) next 方法,返回單個元素,此外還要實現(xiàn) iter 方法,返回迭代器本身。
因此,迭代器可以迭代,但是可迭代的對象不是迭代器。
除了 iter 方法之外,你可能還想在 Sentence 類中實現(xiàn) next 方法,讓
Sentence 實例既是可迭代的對象,也是自身的迭代器。
可是,這種想法非常糟糕。根據(jù)有大量 Python 代碼審查經(jīng)驗的 Alex Martelli 所說,這也是常見的反模式。
python的正確解決之道生成器函數(shù)
import re import reprlib RE_WORD = re.compile("w+") class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __repr__(self): return "Sentence(%s)" % reprlib.repr(self.text) def __iter__(self): for word in self.words: yield word return s = Sentence(""The time has come," the Walrus said,") for word in s: print(word)
? 這個 return 語句不是必要的;這個函數(shù)可以直接“落空”,自動返回。不管有沒有
return 語句,生成器函數(shù)都不會拋出 StopIteration 異常,而是在生成完全部值之后會直接退出。
? 不用再多帶帶定義一個迭代器類!
迭代器其實是生成器對象,每次調(diào)用 iter 方法都會自動創(chuàng)建,因為這里的 iter 方法是生成器函數(shù)。生成器函數(shù)的工作原理
只要 Python 函數(shù)的定義體中有 yield 關(guān)鍵字,該函數(shù)就是生成器函數(shù)。調(diào)用生成器函數(shù)時,會返回一個生成器對象。也就是說,生成器函數(shù)是生成器工廠
def gen_123(): # ? yield 1 # ? yield 2 yield 3 print(gen_123) print(gen_123()) for i in gen_123(): # ? print(i) g = gen_123() # ? print(next(g)) print(next(g)) print(next(g)) ## 報錯 StopIteration # print(next(g))
? 仔細看,gen_123 是函數(shù)對象。
? 為了仔細檢查,我們把生成器對象賦值給 g。
? 生成器函數(shù)的定義體執(zhí)行完畢后,生成器對象會拋出 StopIteration 異常。
把生成器傳給next(...) 函數(shù)時,生成器函數(shù)會向前,執(zhí)行函數(shù)定義體中的下一個 yield 語句,返回產(chǎn)出的值,并在函數(shù)定義體的當前位置暫停。
最終,函數(shù)的定義體返回時,外層的生成器對象會拋出 StopIteration 異常——這一點與迭代器協(xié)議一致。
惰性實現(xiàn)import re import reprlib RE_WORD = re.compile("w+") class Sentence: def __init__(self, text): self.text = text def __repr__(self): return "Sentence(%s)" % reprlib.repr(self.text) def __iter__(self): # 返回一個迭代器 for match in RE_WORD.finditer(self.text): yield match.group()
re.finditer 函數(shù)是 re.findall 函數(shù)的惰性版本,返回的不是列表,而是一個生成
器,按需生成 re.MatchObject 實例。
如果有很多匹配,re.finditer 函數(shù)能節(jié)省大量內(nèi)存。
我們要使用這個函數(shù)讓第 4 版 Sentence 類變得懶惰,即只在需要時才生成下一個單詞。
? 不再需要 words 列表。
? finditer 函數(shù)構(gòu)建一個迭代器,包含 self.text 中匹配 RE_WORD 的單詞,產(chǎn)出
MatchObject 實例。
? match.group() 方法從 MatchObject 實例中提取匹配正則表達式的具體文本。
生成器表達式可以理解為列表推導的惰性版本:不會迫切地構(gòu)建列表,而是返回一個生成器,按需惰性生成元素。
def gen_AB(): # ? print("start") yield "A" print("continue") yield "B" print("end.") res1 = [x * 3 for x in gen_AB()] for i in res1: # ? print("-->", i) res2 = (x * 3 for x in gen_AB()) # ? print(res2) # ? for i in res2: # ? print("-->", i)
? 列表推導迫切地迭代 gen_AB() 函數(shù)生成的生成器對象產(chǎn)出的元素:"A" 和 "B"。注意,下面的輸出是 start、continue 和 end.。
? 這個 for 循環(huán)迭代列表推導生成的 res1 列表。
? 把生成器表達式返回的值賦值給 res2。只需調(diào)用 gen_AB() 函數(shù),雖然調(diào)用時會返回
一個生成器,但是這里并不使用。
? res2 是一個生成器對象。
? 只有 for 循環(huán)迭代 res2 時,gen_AB 函數(shù)的定義體才會真正執(zhí)行。
for 循環(huán)每次迭代時會隱式調(diào)用 next(res2),前進到 gen_AB 函數(shù)中的下一個yield 語句。
注意,gen_AB 函數(shù)的輸出與 for 循環(huán)中 print 函數(shù)的輸出夾雜在一起。
何時使用生成器表達式根據(jù)我的經(jīng)驗,選擇使用哪種句法很容易判斷:如果生成器表達式要分成多行寫,我傾向
于定義生成器函數(shù),以便提高可讀性。此外,生成器函數(shù)有名稱,因此可以重用。
class ArithmeticProgression: def __init__(self, begin, step, end=None): # ? self.begin = begin self.step = step self.end = end # None -> 無窮數(shù)列 def __iter__(self): result = type(self.begin + self.step)(self.begin) # ? forever = self.end is None # ? index = 0 while forever or result < self.end: # ? yield result # ? index += 1 result = self.begin + self.step * index # ?
? 為了提高可讀性,我們創(chuàng)建了 forever 變量,如果 self.end 屬性的值是 None那么forever 的值是 True,因此生成的是無窮數(shù)列。
? 這個循環(huán)要么一直執(zhí)行下去,要么當 result 大于或等于 self.end 時結(jié)束。如果循環(huán)退出了,那么這個函數(shù)也隨之退出。
在示例 14-11 中的最后一行,我沒有直接使用 self.step 不斷地增加 result,
而是選擇使用 index 變量,把 self.begin 與 self.step 和 index 的乘積加,計算 result 的各個值,
以此降低處理浮點數(shù)時累積效應致錯的風險。
標準庫里有大量的生成器輪子 page408 深入分析iter函數(shù)可是,iter 函數(shù)還有一個鮮為人知的用法:傳入兩個參數(shù),使用常規(guī)的函數(shù)或任何可調(diào)
用的對象創(chuàng)建迭代器。
這樣使用時,第一個參數(shù)必須是可調(diào)用的對象,用于不斷調(diào)用(沒有參數(shù)),產(chǎn)出各個值;
第二個值是哨符,這是個標記值,當可調(diào)用的對象返回這個值時,觸發(fā)迭代器拋出 StopIteration 異常,而不產(chǎn)出哨符。
iter 函數(shù)擲骰子,直到擲出 1 點為止
from random import randint def d6(): return randint(1, 6) d6_iter = iter(d6, 1) print(d6_iter) for roll in d6_iter: print(roll)總結(jié)
迭代器 都有 iter這個方法(函數(shù)), (每個都調(diào)用__getitem__做兼容) next()到底.
for每次迭代都是next()
對付大文件,大內(nèi)存. 先返回迭代器對象, 在執(zhí)行迭代器的具體 操作
python 用yield 作為生成器的特征
send可以發(fā)送給 那個狀態(tài)的生成器
iter別的用法 有兩個參數(shù) 第二個是哨符 (遇到就停了)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42044.html
摘要:但是,最好使用差異化的類型定義,函數(shù)簽名如下其實二者說的是同一件事。后者的返回值和初始函數(shù)的返回值相同,即。破壞式更新和函數(shù)式更新的比較三的延遲計算的設(shè)計者們在將引入時采取了比較特殊的方式。四匹配模式語言中暫時并未提供這一特性,略。 一、無處不在的函數(shù) 一等函數(shù):能夠像普通變量一樣使用的函數(shù)稱為一等函數(shù)(first-class function)通過::操作符,你可以創(chuàng)建一個方法引用,...
摘要:當前狀態(tài)可以使用函數(shù)確定,該函數(shù)會返回下述字符串中的一個。解釋器正在執(zhí)行。打印消息,然后協(xié)程終止,導致生成器對象拋出異常。實例運行完畢后,返回的值綁定到上。 協(xié)程 協(xié)程可以身處四個狀態(tài)中的一個。 當前狀態(tài)可以使用inspect.getgeneratorstate(...) 函數(shù)確定,該函數(shù)會返回下述字符串中的一個。 GEN_CREATED 等待開始執(zhí)行。 GEN_RUNNING 解...
摘要:第一章數(shù)據(jù)類型隱式方法利用快速生成類方法方法通過下標找元素自動支持切片操作可迭代方法與如果是一個自定義類的對象,那么會自己去調(diào)用其中由你實現(xiàn)的方法。若返回,則會返回否則返回。一個對象沒有函數(shù),解釋器會用作為替代。 第一章 python數(shù)據(jù)類型 1 隱式方法 利用collections.namedtuple 快速生成類 import collections Card = collec...
摘要:第一章數(shù)據(jù)類型隱式方法利用快速生成字典方法方法通過下標找元素自動支持切片操作可迭代方法與如果是一個自定義類的對象,那么會自己去調(diào)用其中由你實現(xiàn)的方法。若返回,則會返回否則返回。一個對象沒有函數(shù),解釋器會用作為替代。 第一章 python數(shù)據(jù)類型 1 隱式方法 利用collections.namedtuple 快速生成字典 import collections Card = coll...
閱讀 3730·2021-11-17 09:33
閱讀 2756·2021-09-22 15:12
閱讀 3357·2021-08-12 13:24
閱讀 2452·2019-08-30 11:14
閱讀 1742·2019-08-29 14:09
閱讀 1334·2019-08-26 14:01
閱讀 3074·2019-08-26 13:49
閱讀 1787·2019-08-26 12:16