Python中的迭代器與生成器使用及說(shuō)明
一、迭代器(Iterator)
1.1 可迭代對(duì)象(Iterable)
可迭代對(duì)象,可以簡(jiǎn)單理解為可遍歷對(duì)象,即能夠使用 for 循環(huán)遍歷的對(duì)象。Python中常見的可迭代對(duì)象有:列表、元組、字符串、集合、range、字典等。
迭代器和生成器都是可迭代對(duì)象。
對(duì)于Python中的任意對(duì)象,只要它定義了可以返回一個(gè)迭代器的 __iter__ 方法,或者定義了可以支持下標(biāo)索引的 __getitem__ 方法,那么它就是一個(gè)可迭代對(duì)象。
對(duì)可迭代對(duì)象使用 __iter__ 方法后,會(huì)返回一個(gè)迭代器。
如何判斷一個(gè)對(duì)象是否為可迭代對(duì)象呢?請(qǐng)看下例。
from collections.abc import Iterable isinstance([1, 2, 3], Iterable) ?# True isinstance((1, 2, 3), Iterable) ?# True isinstance('123', Iterable) ?# True isinstance({1, 2, 3}, Iterable) ?# True isinstance(range(3), Iterable) ?# True isinstance({'key': 'value'}, Iterable) ?# True isinstance(123, Iterable) ?# False
可以看出,我們只需要使用 isinstance(object, Iterable) 即可判斷給定的 object 是否為可迭代對(duì)象。
嚴(yán)格來(lái)講,isinstance() 只會(huì)將有 __iter__ 方法的對(duì)象判斷為 Iterable。
換言之,僅用 __getitem__ 方法實(shí)現(xiàn)的可迭代對(duì)象會(huì)被 isinstance() 誤判為不可迭代對(duì)象。
最正確的做法是直接嘗試 iter(object),如果沒(méi)有報(bào)錯(cuò),則說(shuō)明 object 是可迭代對(duì)象。
1.2 將可迭代對(duì)象轉(zhuǎn)化為迭代器
我們可以將現(xiàn)有的可迭代對(duì)象轉(zhuǎn)化為可迭代器:
s = '12345' myiter = iter(s) myiter # <str_iterator at 0x25e6f40d130>
不斷調(diào)用 next 方法來(lái)依次獲取迭代器的元素:
next(myiter) # '1' next(myiter) # '2' next(myiter) # '3' next(myiter) # '4' next(myiter) # '5' next(myiter) # StopIteration:?
可見迭代器執(zhí)行到最后時(shí)會(huì)拋出一個(gè) StopIteration 異常。
為避免這種異常,我們完全可以用更簡(jiǎn)單的 for 循環(huán)去遍歷:
for e in myiter: ? ? print(e) # 1 # 2 # 3 # 4 # 5
1.3 構(gòu)造迭代器
構(gòu)造一個(gè)迭代器只需要在自定義的類中實(shí)現(xiàn)兩個(gè)方法:__iter__ 和 __next__ 。
- 迭代器是一個(gè)可以記住遍歷位置的對(duì)象。
- 迭代器對(duì)象會(huì)從第一個(gè)元素開始訪問(wèn),直到所有元素都被訪問(wèn)為止,且只能前進(jìn)不能后退。
當(dāng)我們構(gòu)造類時(shí),必須要有一個(gè)名為 __init__() 的函數(shù),該函數(shù)可以在實(shí)例化時(shí)進(jìn)行一些初始化。
__iter__()
方法的行為類似,可以執(zhí)行操作(初始化等),但必須始終返回迭代器對(duì)象本身。__next__()
方法還允許你進(jìn)行其他操作,并且必須返回序列中的下一項(xiàng)。
class MyIter: ? ? def __iter__(self): ? ? ? ? self.count = 1 ? ? ? ? return self ? ?? ? ? def __next__(self): ? ? ? ? x = self.count ? ? ? ? self.count += 1 ? ? ? ? return x
我們創(chuàng)建了一個(gè)返回?cái)?shù)字的迭代器,每次序列的數(shù)值都將 +1。
myiter = iter(MyIter()) next(myiter) # 1 next(myiter) # 2 next(myiter) # 3
如果我們一直調(diào)用 next() 的方法,則序列的值將會(huì)無(wú)限遞增下去。即如果我們使用 for 循環(huán)去遍歷上述迭代器,循環(huán)將永遠(yuǎn)進(jìn)行下去…
myiter = iter(MyIter()) for e in myiter: ? ? print(e) # 循環(huán)將一直進(jìn)行下去...
為了防止迭代永遠(yuǎn)進(jìn)行下去,我們可以在迭代次數(shù)達(dá)到一定值時(shí)拋出 StopIteration 異常。
class MyIter: ? ? def __iter__(self): ? ? ? ? self.count = 1 ? ? ? ? return self ? ?? ? ? def __next__(self): ? ? ? ? if self.count <= 5: ? ? ? ? ? ? x = self.count ? ? ? ? ? ? self.count += 1 ? ? ? ? ? ? return x ? ? ? ? else: ? ? ? ? ? ? raise StopIteration
這樣再執(zhí)行 for 循環(huán)就不會(huì)一直進(jìn)行下去了:
myiter = iter(MyIter()) for e in myiter: ? ? print(e) # 1 # 2 # 3 # 4 # 5
二、生成器(Generator)
在Python中,一邊迭代(循環(huán))一邊計(jì)算的機(jī)制,稱為生成器。生成器能夠迭代的關(guān)鍵是因?yàn)樗幸粋€(gè) __next__ 方法。
為什么要有生成器呢?我們知道,列表中的所有數(shù)據(jù)都存儲(chǔ)在內(nèi)存中,如果有海量數(shù)據(jù)的話將會(huì)非常消耗內(nèi)存。很多時(shí)候,我們只需要訪問(wèn)列表中前面的元素,這樣一來(lái)后面的元素所占用的空間就白白浪費(fèi)了。
如果列表元素能夠按照某種算法推算出來(lái),那我們就可以在循環(huán)的過(guò)程中不斷推算出后續(xù)的元素,這樣就不必創(chuàng)建完整的列表,從而節(jié)省了大量的空間(即用多少就生成多少)。
有以下兩種常用方法來(lái)創(chuàng)建生成器:
將列表解析式中的 [] 改為 ()。在自定義的函數(shù)中使用 yield 關(guān)鍵字。此時(shí)這個(gè)函數(shù)就不再是一個(gè)普通函數(shù),而是一個(gè)生成器,調(diào)用該函數(shù)就是創(chuàng)建了一個(gè)生成器對(duì)象。
2.1 使用 () 構(gòu)造生成器
比較以下兩段代碼:
a = [x for x in range(3)] type(a) # list
a = (x for x in range(3)) type(a) # generator
我們還可以比較列表解析式和生成器的耗時(shí):
tic = time.time() a = sum([x for x in range(10000000)]) toc = time.time() print(toc - tic) # 0.9081981182098389
tic = time.time() a = sum((x for x in range(10000000))) toc = time.time() print(toc - tic) # 0.6906485557556152
我們當(dāng)然可以對(duì)生成器使用 next() 方法:
next(a) # 0 next(a) # 1 next(a) # 2 next(a) # StopIteration:?
但一般我們不會(huì)用 next() 來(lái)獲取下一個(gè)返回值,而是直接使用 for 循環(huán)來(lái)迭代。
2.2 使用帶有 yield 關(guān)鍵字的函數(shù)構(gòu)造生成器
帶有 yield 的函數(shù)不再是一個(gè)普通函數(shù),而是一個(gè)生成器。
yield 相當(dāng)于return一個(gè)值,并且記住這個(gè)返回的位置,下次迭代時(shí),代碼從 yield 的下一條語(yǔ)句開始執(zhí)行。
我們可以通過(guò)下面的例子先來(lái)理解一下:
def num(): ? ? print('開始執(zhí)行') ? ? for i in range(5): ? ? ? ? yield i ? ? ? ? print('繼續(xù)執(zhí)行') mygen = num() type(mygen) # generator
由此,我們成功創(chuàng)建了一個(gè)生成器對(duì)象。接下來(lái)調(diào)用 next 方法觀察這個(gè)生成器是如何工作的:
next(mygen) # 開始執(zhí)行 # 0 next(mygen) # 繼續(xù)執(zhí)行 # 1 next(mygen) # 繼續(xù)執(zhí)行 # 2 next(mygen) # 繼續(xù)執(zhí)行 # 3 next(mygen) # 繼續(xù)執(zhí)行 # 4 next(mygen) # StopIteration:
當(dāng)然我們也可以使用 for 循環(huán)來(lái)遍歷這個(gè)生成器:
for step in mygen: ? ? print(step) # 開始執(zhí)行 # 0 # 繼續(xù)執(zhí)行 # 1 # 繼續(xù)執(zhí)行 # 2 # 繼續(xù)執(zhí)行 # 3 # 繼續(xù)執(zhí)行 # 4 # 繼續(xù)執(zhí)行
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
matplotlib相關(guān)系統(tǒng)目錄獲取方式小結(jié)
這篇文章主要介紹了matplotlib相關(guān)系統(tǒng)目錄獲取方式小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Python中的內(nèi)置函數(shù)isdigit()
這篇文章主要介紹了Python中的內(nèi)置函數(shù)isdigit(),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Python通過(guò)命令開啟http.server服務(wù)器的方法
這篇文章主要給大家介紹了關(guān)于Python通過(guò)命令開啟http.server服務(wù)器的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11Python中關(guān)于元組 集合 字符串 函數(shù) 異常處理的全面詳解
本篇文章介紹了我在學(xué)習(xí)python過(guò)程中對(duì)元組、集合、字符串、函數(shù)、異常處理的總結(jié),通讀本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下2021-10-10Jupyter Notebook的連接密碼 token查詢方式
這篇文章主要介紹了Jupyter Notebook的連接密碼 token查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04在Python代碼中執(zhí)行Linux命令的詳細(xì)用法教程
在Python開發(fā)過(guò)程中,經(jīng)常需要執(zhí)行Linux系統(tǒng)命令來(lái)完成各種任務(wù),Python提供了多種方式來(lái)調(diào)用和執(zhí)行系統(tǒng)命令,本文將詳細(xì)介紹如何在Python代碼中執(zhí)行Linux命令,并結(jié)合實(shí)際案例來(lái)演示這些方法的使用,需要的朋友可以參考下2024-07-07詳解四種Python中基本形態(tài)學(xué)濾波的實(shí)現(xiàn)
最基礎(chǔ)的形態(tài)學(xué)操作有四個(gè),分別是腐蝕、膨脹、開計(jì)算和閉計(jì)算。這篇文章主要介紹了這四種形態(tài)學(xué)濾波的實(shí)現(xiàn),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04