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