摘要:迭代器迭代器在版本中被加入它為類序列對象提供了一個類序列的接口。其中方法返回迭代器對象本身方法返回容器的下一個元素,在結(jié)尾時引發(fā)異常。迭代器協(xié)議迭代器協(xié)議即實現(xiàn)與方法。
迭代器
迭代器在 Python 2.2 版本中被加入, 它為類序列對象提供了一個類序列的接口。 Python 的迭代無縫地支持序列對象, 而且它還允許迭代非序列類型, 包括用戶定義的對象。即迭代器可以迭代不是序列但表現(xiàn)出序列行為的對象, 例如字典的 key , 一個文件的行, 等等。迭代器有以下特性:
提供了可擴(kuò)展的迭代器接口.
對列表迭代帶來了性能上的增強(qiáng).
在字典迭代中性能提升.
創(chuàng)建真正的迭代接口, 而不是原來的隨機(jī)對象訪問.
與所有已經(jīng)存在的用戶定義的類以及擴(kuò)展的模擬序列和映射的對象向后兼容
迭代非序列集合(例如映射和文件)時, 可以創(chuàng)建更簡潔可讀的代碼.
迭代器對象即實現(xiàn)了迭代器協(xié)議的對象,在 Python 中,支持迭代器協(xié)議就是實現(xiàn)對象的 __iter__() 和 next() 方法(注:在 Python3 中被改為 next 方法)。其中 __iter__() 方法返回迭代器對象本身;next() 方法返回容器的下一個元素,在結(jié)尾時引發(fā) StopIteration 異常。
迭代器協(xié)議迭代器協(xié)議即實現(xiàn) __iter__() 與 next() 方法。這兩個方法是迭代器最基本的方法,一個用來獲得迭代器對象,一個用來獲取容器中的下一個元素。對于可迭代對象,可以使用內(nèi)建函數(shù) iter() 來獲取它的迭代器對象:
li = [1, 2] it = iter(li) print it print it.next() print it.next() print it.next()
結(jié)果如下所示:
1 2 Traceback (most recent call last): File "iter.py", line 21, in print it.next() StopIteration
列表(list)本身是可迭代的,通過 iter() 方法可以獲得其迭代器對象,然后就可以通過 next() 方法來訪問 list 中的元素。當(dāng)容器中沒有可以訪問的元素時, next() 方法將會拋出一個 StopIteration 的異常,從而終止迭代器。當(dāng)我們使用 for 語句的時候,for 語句就會自動的通過 __iter__() 方法來獲得迭代器對象,并且通過 next() 方法來獲取下一個元素,遇到 StopIteration 異常時會自動結(jié)束迭代。
自定義迭代器自己創(chuàng)建迭代器實際上就是實現(xiàn)一個帶有 __iter__() 方法和 next() 方法的類,用該類創(chuàng)建的實例即是可迭代對象。例如我們用迭代器來實現(xiàn)斐波那契數(shù)列:
class Fibs: def __init__(self): self.a = 0 self.b = 1 def next(self): self.a, self.b = self.b, self.a + self.b return self.a def __iter__(self): return self fibs = Fibs() // 這將得到一個無窮的數(shù)列 for f in fibs: if f > 1000: print f break else: print f
這里有一個問題,大多數(shù)的序列或者類序列都不是無窮的,所以在達(dá)到一定條件后就該終止。因此我們需要在序列或者類序列需要結(jié)束時引發(fā) StopIteration 異常:
class MyRange(object): def __init__(self, n): self.idx = 0 self.n = n def __iter__(self): return self def next(self): if self.idx < self.n: val = self.idx self.idx += 1 return val else: raise StopIteration() myRange = MyRange(3) for i in myRange: print i可迭代對象和迭代器對象
可迭代對象即具有 __iter__() 方法的對象,該方法可獲取其迭代器對象。迭代器對象即具有 next() 方法的對象。也就是說,一個實現(xiàn)了 __iter_() 的對象是可迭代的,一個實現(xiàn)了 next() 方法的對象則是迭代器??傻鷮ο笠部梢允堑鲗ο螅缥募ο?。此時可迭代對象自己有 next() 方法,而其 __iter() 方法返回的就是它自己。對于許多內(nèi)置對象及其派生對象,如 list、dict 等,由于需要支持多次打開迭代器,因此自己并非迭代器對象,需要用 __iter_() 方法返回其迭代器對象,并用迭代器對象來訪問其它元素。
以上例子中的 myRange 這個對象就是一個可迭代對象,同時它本身也是一個迭代器對象。對于一個可迭代對象,如果它本身又是一個迭代器對象,就會有這樣一個問題,其沒有辦法支持多次迭代。如下所示:
myRange = MyRange(3) print myRange is iter(myRange) print [i for i in myRange] print [i for i in myRange]
運行結(jié)果:
True [0, 1, 2] []
為了解決上面的問題,可以分別定義可迭代類型對象和迭代器類型對象;然后可迭代類型對象的 __iter__() 方法可以獲得一個迭代器類型的對象。如下所示:
class Zrange: def __init__(self, n): self.n = n def __iter__(self): return ZrangeIterator(self.n) class ZrangeIterator: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration() zrange = Zrange(3) print zrange is iter(zrange) print [i for i in zrange] print [i for i in zrange]
運行結(jié)果:
False [0, 1, 2] [0, 1, 2]
另外, reversed() 內(nèi)建函數(shù)將返回一個反序訪問的迭代器,enumerate() 內(nèi)建函數(shù)同樣也返回迭代器。例如可以用 enumerate() 函數(shù)遍歷列表:
ll = [1, 2, 3] print enumerate(ll) /* 優(yōu)雅的遍歷列表 */ for i, ele in enumerate(ll): print i, ll[i]生成器
迭代器和生成器可能是近幾年引入的最強(qiáng)大的兩個特性。生成器是一種用普通的函數(shù)語法定義的迭代器,也就是說生成器實際上就是一個函數(shù)。但是生成器不用 return 返回,而是用 yield 一次返回一個結(jié)果,在每個結(jié)果之間掛起和繼續(xù)它們的狀態(tài),來自動實現(xiàn)迭代協(xié)議。任何包含 yield 語句的函數(shù)稱為生成器。yield 被人們優(yōu)雅的稱之為語法糖,意思就是包在里邊的甜心。在 yield 的內(nèi)部是一個狀態(tài)機(jī),維護(hù)著掛起和繼續(xù)的狀態(tài)。
生成器執(zhí)行流程先看看下邊的列子:
def Zrange(n): print "beginning of Zrange" i = 0 while i < n: print "before yield", i yield i i += 1 print "after yield", i print "endding of Zrange" zrange = Zrange(3) print "------------" print zrange.next() print "------------" print zrange.next() print "------------" print zrange.next() print "------------" print zrange.next()def flatten(nested): result = [] try: # 不要迭代類似字符串的對象 try: nested + "" except TypeError: pass else: raise TypeError for sublist in nested: for element in flatten(sublist): result.append(element) except TypeError: result.append(nested) return result print "------------"
執(zhí)行結(jié)果:
-------------------- beginning of Zrange before yield 0 0 -------------------- after yield 1 before yield 1 1 -------------------- after yield 2 before yield 2 2 -------------------- after yield 3 endding of Zrange Traceback (most recent call last): File "one.py", line 38, inprint zrange.next() StopIteration
通過結(jié)果可以看到:
當(dāng)調(diào)用生成器函數(shù)的時候,函數(shù)只是返回了一個生成器對象,并沒有 執(zhí)行。
當(dāng)next()方法第一次被調(diào)用的時候,生成器函數(shù)才開始執(zhí)行,執(zhí)行到y(tǒng)ield語句處停止
next()方法的返回值就是yield語句處的參數(shù)(yielded value)
當(dāng)繼續(xù)調(diào)用next()方法的時候,函數(shù)將接著上一次停止的yield語句處繼續(xù)執(zhí)行,并到下一個yield處停止;如果后面沒有yield就拋出StopIteration異常
遞歸生成器生成器可以向函數(shù)一樣進(jìn)行遞歸使用,下面列舉兩個示例:
對一個序列進(jìn)行全排列:
def permutations(li): if len(li) == 0: yield li else: for i in range(len(li)): li[0], li[i] = li[i], li[0] # for item in permutations(li[1:]): yield [li[0]] + item for item in permutations(range(3)): print item
這里的實現(xiàn)思路是,每次取序列中不一樣的元素放在最前面,直到只有一個元素時返回,并將返回結(jié)果往后拼接。
展開多層嵌套的列表:
def flatten(nested): try: # 不要迭代類似于字符串的對象 try: nested + "" except TypeError: pass else: raise TypeError for sublist in nested: for element in flatten(sublist): yield element except TypeError: yield nested print list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))
這里需要注意的是,不應(yīng)該在 flatten 函數(shù)中對類似于字符串的對象進(jìn)行迭代,這樣會導(dǎo)致無窮遞歸,因為一個字符串的第一個元素是另一個長度為1的字符串,而長度為一個字符串的第一個元素就是字符串本身。
通用生成器生成器可以人為是由兩部分組成:生成器的函數(shù)和生成器的迭代器。生成器的函數(shù)是用 def 語句定義的,包含 yield 部分,生成器的迭代器是這個函數(shù)返回的部分。按照一種不是很準(zhǔn)確的說法,兩個實體經(jīng)常被當(dāng)做一個,合起來叫做生成器。如下實例所示:
def simple_generator(): yield 1 print simple_generator print simple_generator()
運行結(jié)果:
生成器方法
send(value)
外部作用域訪問生成器的 send 方法,就像訪問 next() 方法一樣。next()方法可以恢復(fù)生成器狀態(tài)并繼續(xù)執(zhí)行,其實 send() 是除 next() 外另一個恢復(fù)生成器的方法。Python 2.5 中,yield 語句變成了 yield 表達(dá)式,也就是說 yield 可以有一個值,而這個值就是send()方法的參數(shù),所以 send(None) 和 next() 是等效的。同樣,next()和send()的返回值都是 yield語 句處的參數(shù)(yielded value)。使用 send() 方法只有在生成器掛起之后才有意義,如果真想對剛剛啟動的生成器使用 send 方法,則可以將 None 作為參數(shù)進(jìn)行調(diào)用。也就是說, 第一次調(diào)用時,要使用 next() 語句或 send(None),因為沒有 yield 語句來接收這個值。
throw()
用于在生成器內(nèi)引發(fā)一個異常。
close()
用于停止生成器,調(diào)用它時,會在 yield 運行出引發(fā)一個 GeneratorExit 異常。
使用示例:
def Zrange(n): i = 0 while i < n: val = yield i print "val is", val i += 1 zrange = Zrange(5) print zrange.next() print zrange.next() print zrange.send("hello") print zrange.next() #print zrange.next() zrange.close() print zrange.send("world")模擬生成器
在舊的 Python 版本中并不支持生成器,那么我們可以用普通的函數(shù)來模擬生成器。如下所示:
def flatten(nested): result = [] try: # 不要迭代類似字符串的對象 try: nested + "" except TypeError: pass else: raise TypeError for sublist in nested: for element in flatten(sublist): result.append(element) except TypeError: result.append(nested) return result
盡管這個版本可能不適用于所有的生成器,但對大多數(shù)生成器來說是可行的。比如,它不適用于一個無限的生成器。
列表解析和生成器表達(dá)式 列表解析列表解析( List comprehensions, 或縮略為 list comps ) 來自函數(shù)式編程語言 Haskell . 它是一個非常有用, 簡單, 而且靈活的工具, 可以用來動態(tài)地創(chuàng)建列表。其語法結(jié)構(gòu)為:
[expr for iter_var in iterable]
這個語句的核心是 for 循環(huán), 它迭代 iterable 對象的所有條目. 前邊的 expr 應(yīng)用于序列的每個成員, 最后的結(jié)果值是該表達(dá)式產(chǎn)生的列表。 迭代變量并不需要是表達(dá)式的一部分。例如用 lambda 函數(shù)計算序列成員的平方的表達(dá)是為:
map(lambda x: x ** 2, range(6))
這可以用列表解析來改寫:
[x ** 2 for x in range(6)]
列表解析的表達(dá)式可以取代內(nèi)建的 map() 函數(shù)以及 lambda , 而且效率更高。結(jié)合 if 語句,列表解析還提供了一個擴(kuò)展版本的語法:
[expr for iter_var in iterable if cond_expr]
這個語法在迭代時會過濾/捕獲滿足條件表達(dá)式 cond_expr 的序列成員。例如挑選出序列中的奇數(shù)可以用下邊的方法:
[x for x in seq if x % 2]
列表解析還有很多巧妙的應(yīng)用:
迭代一個有三行五列的矩陣:
[(x+1,y+1) for x in range(3) for y in range(5)]
計算出所有非空白字符的數(shù)目:
f = open("hhga.txt", "r")生成器表達(dá)式len([word for line in f for word in line.split()])
生成器表達(dá)式是列表解析的一個擴(kuò)展。列表解析的一個不足就是必要生成所有的數(shù)據(jù), 用以創(chuàng)建整個列表。這可能對有大量數(shù)據(jù)的迭代器有負(fù)面效應(yīng)。生成器表達(dá)式通過結(jié)合列表解析和生成器解決了這個問題。生成器表達(dá)式在 Python 2.4 被引入, 它與列表解析非常相似,而且它們的基本語法基本相同; 不過它并不真正創(chuàng)建數(shù)字列表, 而是返回一個生成器,這個生成器在每次計算出一個條目后,把這個條目“產(chǎn)生”(yield)出來。生成器表達(dá)式使用了"延遲計算"(lazy evaluation), 所以它在使用內(nèi)存上更有效。生成器表達(dá)式語法:
(expr for iter_var in iterable if cond_expr)
生成器并不會讓列表解析廢棄, 它只是一個內(nèi)存使用更友好的結(jié)構(gòu), 基于此, 有很多使用生 成器地方,如下所示:
快速地計算文件大小:
上面我們用列表解析計算出了文件中非空白字符的數(shù)目,那么只要用 sum() 函數(shù)對每個單詞的長度求和,則可大致計算出文件的大小。sum() 函數(shù)的參數(shù)不僅可以是列表,還可以是可迭代對象,比如生成器表達(dá)式。這里我們用生成器表達(dá)式改寫整個過程:
sum(len(word) for line in data for word in line.split())
交叉配對:
生成器表達(dá)式就好像是懶惰的列表解析(這反而成了它主要的優(yōu)勢)。它還可以用來處理其他列表或生成器:
rows = [1, 2, 3, 17] def cols(): # example of simple generator yield 56 yield 2 yield 1 x_product_pairs = ((i, j) for i in rows for j in cols()) for pair in x_product_pairs: print pair
尋找文件最長的行:
def longest(filename): glines = (x.strip() for x in open(filename)) return max([len(l) for l in glines]) # Script starts from here if __name__ == "__main__": print longest("/etc/hosts")
這個例子摘自 《Python核心編程》 中生成器表達(dá)式一節(jié),作者在原書中只用了一行代碼來實現(xiàn)這個功能,即:
return max(len(x.strip()) for x in open("/etc/motd"))
這行代碼會報出如下錯誤:
TypeError: object of type "generator" has no len()
也就是說生成器沒有 len() 方法,所以這樣并不可行,但是用列表解析則可以用一行實現(xiàn):
return max([len(x.strip()) for x in open("/etc/motd")])
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/43095.html
摘要:前言首先,明確可迭代對象迭代器和生成器這三個概念。迭代器對象傳送門之迭代器實現(xiàn)原理首先明確它是一個帶狀態(tài)的對象。生成器是一種特殊的迭代器,它的返回值不是通過而是用。 前言 首先,明確可迭代對象、迭代器和生成器這三個概念。 可迭代對象(Iterable) 可迭代對象(Iterable Object),簡單的來理解就是可以使用 for 來循環(huán)遍歷的對象。比如常見的 list、set和di...
摘要:裝飾器的應(yīng)用場景比如插入日志,性能測試,事務(wù)處理,緩存等等場景。裝飾器完美的遵循了這個開放封閉原則。迭代器迭代器遵循迭代器協(xié)議必須擁有方法和方法。直到函數(shù)執(zhí)行結(jié)束。調(diào)用相關(guān)函數(shù)用于檢查一個對象是否是可調(diào)用的。 裝飾器 裝飾器的含義: 1.裝飾器本質(zhì)上就是一個python函數(shù),他可以讓其他函數(shù)在不需要做任何代碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函數(shù)對象。2.裝飾器的應(yīng)用...
摘要:第四種選擇是在不同的線程中運行生產(chǎn)者和消費者。包含語句的函數(shù)被稱為生成器函數(shù)。然后引發(fā)一個異常,表明迭代器已經(jīng)耗盡。換句話說,未捕獲的異常終結(jié)了生成器的使用壽命。 showImg(https://segmentfault.com/img/bVbntUq?w=4272&h=2848);我正打算寫寫 Python 的生成器,然而查資料時發(fā)現(xiàn),引入生成器的 PEP 沒人翻譯過,因此就花了點時...
閱讀 849·2021-08-23 09:46
閱讀 998·2019-08-30 15:44
閱讀 2650·2019-08-30 13:53
閱讀 3107·2019-08-29 12:48
閱讀 3965·2019-08-26 13:46
閱讀 1884·2019-08-26 13:36
閱讀 3571·2019-08-26 11:46
閱讀 1496·2019-08-26 10:48