Python標准模塊--Iterators和Generators


1 模塊簡介

當你開始使用Python編程時,你或許已經使用了iterators(迭代器)和generators(生成器),你當時可能並沒有意識到。在本篇博文中,我們將會學習迭代器和生成器是什么。當然,我們也會了解如何創建它們,在我們需要的時候,就可以創建屬於我們自己的迭代器和生成器。

2 模塊使用

2.1 迭代器

迭代器是一個允許你在一個容器上進行迭代的對象。Python的迭代器主要通過兩個方法實現:__iter__和__next__。__iter__要求你的容器支持迭代。它會返回迭代器對象本身。如果你想創建一個迭代器對象,你還需要定義__next__方法,它將會返回容器的下一個元素。

注意: 在Python 2中,命名習慣有所不同,__iter__保持不變,__next__ 改為next。

為了對這些概念更加清晰,讓我們回顧下面的兩個定義:

  • 可迭代對象(iterable),只定義了__iter__方法;
  • 迭代器(iterator),定義了__iter__和__next__兩個方法,__iter__返回迭代器本身,__next__方法返回下一個元素;

所有函數名中有雙下划線的方法,都很神奇,你不需要直接調用__iter__或者__next__。你可以使用for循環或者轉換為列表,Python就會自動替你調用這些方法。當然你或許還是想調用它們,那么你可以使用Python內置的函數iter和next方法。

Python 3 提供了一些序列類型,例如list、tuple和range。list是一個可迭代對象,但不是一個迭代器,因為它沒有實現__next__方法。我們通過下面的例子,可以很容易發現這點。

>>> my_list = [1,2,3] >>> next(my_list) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not an iterator

當我們在這個例子中,嘗試着調用列表的next方法,最終我們接收到一個TypeError異常信息,可以發現列表對象並不是一個迭代器。我們換一個思路,讓我們看下面的代碼:

>>> iter(my_list) <list_iterator object at 0x7f6ec6b00f98> >>> list_iterator = iter(my_list) >>> next(list_iterator) 1 >>> next(list_iterator) 2 >>> next(list_iterator) 3 >>> next(list_iterator) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration

僅僅對列表調用Python內置的iter函數,就可以將列表轉換為迭代器。當你對它調用next方法,直到它遍歷完所有元素,就會收到StopIteration異常信息。讓我們嘗試將列表轉換為迭代器,然后在循環中遍歷它。

>>> for item in iter(my_list): ... print(item) ... 1 2 3

當你使用循環來遍歷一個迭代器,你就不需要調用next方法,你也無需擔心會收到StopIteration異常信息。

2.2 創建屬於你的迭代器

有時候,你也想創建屬於你的迭代器。Python很容易地就可以完成這個任務。正如上一章節提到,你需要做的就是在你的類里實現__iter__和__next__這兩個方法。讓我們創建一個迭代器,這個迭代器可以遍歷字符串。

class MyIterator: def __init__(self,letters): self.letters = letters self.position = 0 def __iter__(self): return self def __next__(self): if self.position >= len(self.letters): raise StopIteration letter = self.letters[self.position] self.position += 1 return letter if __name__ == "__main__": i = MyIterator('abcd') for item in i: print (item)

對於這個例子,在我們的類中,我們僅僅需要實現三個方法。在初始化函數中,我們傳入字符串,創建一個類變量用於引用這個字符串。我們也初始化了一個位置變量,以便於我們知道我們在字符串的位置。__iter__方法僅僅返回它本身,這就是是它所要做的。__next__是這個類中最重要的方法,我們首先檢查位置是否超出字符串的長度,如果超出,就會拋出StopIteration異常信息。然后,我們提取出字符串上該位置的字符,將位置加1,最后返回該字符。

下面的例子中,我們創建一個無限的迭代器。一個無限的迭代器就是可以一直遍歷。你要注意的是當調用這個迭代器時,如果你不加任何限制的話,它們將會導致死循環。

class Doubler(): def __init__(self): self.number = 0 def __iter__(self): return self def __next__(self): self.number += 1 return self.number * self.number if __name__ == "__main__": doubler = Doubler() count = 0 for number in doubler: print (number) if count > 5: break count += 1

在這段代碼中,我們沒有向迭代器中傳入任何參數。我們僅僅以此為例進行說明。為了不陷入死循環,我們在開始遍歷迭代器之前,加入一個計數器。最后,我們開始遍歷,當計時器超過5,循環就會中斷。

2.3 生成器

一個普通的Python函數經常返回一個值,無論它是列表、整數還是其他對象。但是如果你想調用一個函數,這個函數能否產生一系列值呢?這個就是生成器誕生的原因。生成器的工作機制是保存它所停止(或者說它已經產生)的位置,然后給主調函數返回一個值。不是返回一個調用函數的執行,生成器僅僅返回一個臨時的控制返回。為了完成這個功能,生成器函數需要使用Python的 yield 語句。

注意: 在其它編程語言中,生成器可能叫做協同程序;

讓我們先創建一個簡單的生成器,

>>> def doubler_generator(): ... number = 2 ... while True: ... yield number ... number *= number ... >>> doubler = doubler_generator() >>> next(doubler) 2 >>> next(doubler) 4 >>> next(doubler) 16 >>> type(doubler) <class 'generator'>

這個生成器僅僅是創建一個無限的序列。你可以一直對它調用next方法,它絕不會用完所有的值。因為你可以遍歷生成器,因此,一個生成器也可以認為是一類迭代器,但是沒有人會真正把它們聯系在一起。其實,生成器也定義了__next__方法,我們觀察上面的例子,這就是為什么next函數可以正常執行。

讓我們來看另一個例子,這個例子中只產生3個元素,而不是無限的序列。

>>> def silly_generator(): ... yield "Python" ... yield "Rocks" ... yield "So do you!" ... >>> gen = silly_generator() >>> next(gen) 'Python' >>> next(gen) 'Rocks' >>> next(gen) 'So do you!' >>> next(gen) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration

在這個例子中,我們創建了一個使用yield語句3次的生成器。在每個實例中,它產生不同的字符串。你可以認為yield是生成器的返回語句。無論何時你調用yield,函數都會中止,然后保存它的狀態。然后它產生出值,這就是你在上面的例子里通過終端看到的輸出。如果我們在函數中有一些變量,那么這些變量也會被保存。

當你看到StopIteration,你應該了解到你已經耗盡了這個迭代器。這就意味着這個迭代器已經用完了。你也可以在上面的迭代器部分看到這個特點。

無論何時我們調用next方法,生成器在它上次離開的地方開始,然后產生下一個值或者我們結束函數,生成器結束。如果你一直不調用next方法,這個狀態就會永遠保持下去。

讓我們實例化這個生成器,然后使用循環來遍歷它。

>>> gen = silly_generator() >>> for item in gen: ... print(item) ... Python Rocks So do you!

我們創建一個新的生成器實例,是因為當我們遍歷完這個生成器的時候,我們不希望產生任何事情。這是因為我們已經遍歷完這個生成器的實例的所有值。所以在這個例子中,我們創建一個實例,然后通過循環來遍歷它,並打印產生出的值。當生成器遍歷完,for循環會替我們處理StopIteration異常,然后中斷循環。

生成器的一個優點就是它可以遍歷很大的數據集,然后一次只返回一部分數據。下面的例子就是當我們打開一個文件,然后逐行返回數據,

with open("temp.txt") as fobj: for line in fobj: print line

當我們以這種方式遍歷時,Python會將文件對象轉換為一個生成器。這種方式允許我們處理空間太大以至於無法讀入到內存的文件。對於任意的大數據集,對於這些大數據集,你需要塊操作或者當你需要生成一個大數據集但是它將會填滿你的內存,你就會發現生成器很有用處。

2.4 總結

到這里,你已經了解了什么是迭代器以及如何使用它。你也了解到可迭代對象和迭代器的區別。最后,我們學習了什么是生成器以及你如何使用它。例如,生成器很適合處理內存有效的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM