本系列目的:一篇文章,不求鞭辟入裏,但使駕輕就熟。
迭代是數據處理的基石,在掃描內存沒法裝載的數據集時,咱們須要一種惰性獲取數據的能力(即一次獲取一部分數據到內存)。在Python中,具備這種能力的對象就是迭代器。生成器是迭代器的一種特殊表現形式。python
我的認爲生成器是Python中最有用的高級特性之一(甚至沒有之一)。雖然初級編碼中使用寥寥,但隨着學習深刻,會發現生成器是協程,異步等高級知識的基石。Python最有野心的asyncio庫,就是用協程砌造的。git
注:生成器和協程本質相同。PEP342(Python加強提案)增長了生成器的send()方法,使其變身爲協程。如此以後,生成器生成數據,協程消費數據。雖然本質相同,可是因爲從理念上說協程跟迭代沒有關係,而且糾纏生成器和協程的區別與聯繫會引爆本身的大腦,因此應該將這兩個概念區分。此處說本質相贊成爲:理解生成器原理以後,理解增長了send方法,可是實現方式幾乎相同的協程會更加輕鬆(這段話看不懂沒有關係,船到橋頭天然直,學到協程天然懂)。
迭代器github
在學習生成器以前,先要了解迭代器。顧名思義,迭代器即具備迭代功能的對象。在Python中,能夠認爲迭代器能夠經過不斷迭代,產生出一個又一個的對象。
可迭代對象和迭代器面試
Python的一致性是靠協議支撐的。一個對象只要遵循如下協議,它就是一個可迭代對象或迭代器。
Python中的一個對象,若是實現了iter方法,而且iter方法返回一個迭代器,那麼它就是可迭代對象。若是實現了iter和next方法,而且iter方法返回一個迭代器,那麼它就是迭代器(有點繞,按住不表,繼續學習)。異步
注:若是對象實現了__getitem__方法,而且索引從0開始,那麼也是可迭代對象。此hack爲兼容性考慮。只需切記,若是你要實現可迭代對象和可迭代器,那麼請遵循以上協議。
可迭代對象與迭代器的關係async
話很少說,上代碼。函數
class Iterable: def __init__(self, *args): self.items = args def __iter__(self): return Iterator(self.items) class Iterator: def __init__(self, items): self.items = items self.index = 0 def __iter__(self): return self def __next__(self): try: item = self.items[self.index] except IndexError: raise StopIteration() self.index += 1 return item ins = Iterable(1,2,3,4,5) # 1 for i in ins: print(i) print('the end...') >>> # 2 1 2 3 4 5 the end ...
經過上述代碼迭代一個對象顯得十分囉嗦。好比在Iterable中,iter必需要返回一個迭代器。爲何不能直接用Iterator迭代元素呢?假設咱們經過迭代器來迭代元素,將上述代碼中的#1處以下代碼:學習
ins = Iterator([1,2,3,4,5]) for i in ins: # 3 print(i) for i in ins: # 4 print(i) next(ins) # 5 print('the end...') >>> # 6 1 2 3 4 5 ... File "/home/disk/test/a.py", line 20, in __next__ # 7 raise StopIteration() the end...
運行上述代碼,會看到#6處的輸出。疑惑的是,#3和#4處運行了兩次for循環,結果只打印一遍全部元素。解釋以下:編碼
從新編寫上述代碼中#3處for循環和#4處for循環,能夠看到對應輸出驗證了咱們的結論。第一次for循環在迭代到元素爲2時跳出循環,第二次for循環繼續迭代同一個迭代器,那麼會繼續上次迭代器結束位置繼續迭代元素。代碼以下:線程
ins = Iterator([1,2,3,4,5]) print('the first for:') for i in ins: # 3 the first for print(i) if i == 2: break print('the second for:') for i in ins: # 4 the second for print(i) print('the end...') >>> # the output the first for: 1 2 the second for: 3 4 5 the end...
因此咱們能夠獲得以下結論:
迭代器支持
引用流暢的Python中的原話,迭代器支持如下6個功能。因爲篇幅所限,點到爲止。你們只要理解了迭代器的原理,理解如下功能天然是水到渠成。
上述代碼已經有舉例,可參考
構建和擴展集合類型
from collections improt abc class NewIterator(abc.Iterator): pass # 放飛自我,實現新的類型
列表推導,字典推導和集合推導
l = [i for i in range(10)] # list d = {i:i for i in range(10)} # dict s = {i for i in range(10)} # set
遍歷文本文件
with open ('a.txt') as f: for line in f: print(line)
元祖拆包
for i, j in [(1, 2), (3, 4)]: print(i, j) >>> 1 2 3 4
調用函數時,使用*拆包實參
def func(a, b, c): print(a, b, c) func(*[1, 2, 3]) # 會將[1, 2, 3]這個list拆開成三個實參,對應a, b, c三個形參傳給func函數
生成器
Python之禪曾經說過,simple is better than complex。鑑於以上代碼中迭代器複雜的實現方式。Python提供了一個更加pythonic的實現方式——生成器。生成器函數就是含有yield關鍵字的函數(目前這種說法是正確的,以後會學到yield from等句法,那麼這個說法就就須要更正了),生成器對象就是調用生成器函數返回的對象。
生成器的實現
將上述代碼修改成生成器實現,以下:
class Iterable: def __init__(self, *args): self.items = args def __iter__(self): # 8 for item in self.items: yield item ins = Iterable(1, 2, 3, 4, 5) print('the first for') for i in ins: print(i) print('the second for') for i in ins: print(i) print('the end...') >>> # 9 the first for 1 2 3 4 5 the second for 1 2 3 4 5 the end...
上述代碼中,可迭代對象的iter方法並無只用了短短數行,就完成了以前Iterator迭代器功能,點贊!
yield關鍵字
要理解以上代碼,就須要理解yield關鍵字,先來看如下最簡單的生成器函數實現
def func(): yield 1 yield 2 yield 3 ins1 = func() ins2 = func() print(func) print(ins1) print(ins2) for i in ins1: print(i) for i in ins1: print(i) print(next(ins2)) print(next(ins2)) print(next(ins2)) print(next(ins2)) >>> <function func at 0x7fcb1e4bde18> <generator object func at 0x7fcb1cc7c0a0> <generator object func at 0x7fcb1cc7c0f8> 1 2 3 1 2 3 File "/home/disk/test/a.py", line 18, in <module> print(next(ins2)) StopIteration
從以上代碼能夠看出:
那麼含有yield關鍵字的生成器函數體是如何執行的呢?請看以下代碼: ```python def f_gen(): # 10 print('start') yield 1 # 11 print('stop') yield 2 # 12 print('next') yield 3 # 13 print('end') for i in f_gen(): # 14 print(i) >>> start 1 stop 2 next 3 end ``` 從上述代碼及其打印結果,咱們能夠得出以下結論: - \#10處代碼代表,生成器函數定義與普通函數無二,只是須要包含有yield關鍵字 - \#14for 循環隱形調用next的時候,會執行到#11處,打印start,而後產出值 1返回給for循環,打印 - for 循環繼續調用next,**從#11處執行到#12處**#,打印stop,而後產出值 2返回給for循環,打印 - for 循環繼續調用next,**從#12處執行到#13處**#,打印next,而後產出值 3返回給for循環,打印 - for 循環繼續調用next,**從#13處執行到函數尾**#,打印end,而後raise一個StopIteration,因爲for循環捕獲異常,程序正常執行 - **綜上所述,yield具備暫停的功能,每次迭代生成器,生成器函數體都會前進到yield語句處,並將yield以後的值拋出(無值拋None)。生成器函數做爲一個工廠函數,實現了可迭代對象中iter函數的功能,能夠每次產出一個新的迭代器實例。因爲使用了特殊的yield關鍵字,它擁有與區別於迭代器的新名字——生成器,它其實與迭代器並沒有二致**
生成器表達式
將列表推導式中的[]改成(),即爲生成器表達式。返回的是一個生成器對象。通常用戶列表推導可是又不須要立馬產生全部值的情景中。
gen = (i for i in range(10)) for i in gen: print(i) for i in gen: # 只能被消費一遍,第二遍無輸出 print(i) print('the end...') >>> 0 1 2 3 4 5 6 7 8 9 the end...
itertools
python的內置模塊itertools提供了對生成器的諸多支持。這裏列舉一個,其它支持請看文檔
gen = itertools.count(1, 2) # 從1開始,步長爲2,不斷產生數值 >>> next(gen) 1 >>> next(gen) 3 >>> next(gen) 5 >>> next(gen) 7 >>> next(gen) 9 >>> next(gen) 11
yield from 關鍵字
yield from 是python3.3中出現的新句法。yield from句法能夠實現委派生成器。
def func(): yield from (i for i in range(5)) gen = func() for i in gen: print(i) >>> 0 1 2 3 4
如上所示,yield from把func做爲了一個委派生成器。for循環能夠經過委派生成器func直接迭代子生成器(i for i in range(5))。不過只是這個取巧遠遠不足以將yield from做爲一個新句法加入到Python中。比起上述代碼的迭代內層循環,新句法更加劇要的功能是委派生成器爲調用者和子生成器創建了一個管道。經過生成器的send方法就能夠在管道中爲兩端傳遞消息。若是使用此方法在程序層面控制線程行爲,就會迸發出強大的能量,它叫作協程。
注意事項
迭代器與生成器功能強大,不過使用中仍是有幾點要注意:
迭代器應該實現iter方法,雖然不少時候不實現此方法頁不會影響代碼運行。實現此方法的最主要緣由有二:
不要把可迭代對象變爲迭代器。緣由有二:
tips
我的以爲迭代器有趣的點
os.walk迭代器能夠深度遍歷目錄,是個大殺器,你值得擁有,快去試試吧。
iter
iter能夠接受兩個位置參數:callable和flag。callable()能夠不斷產出值,若是等於flag,則終止。以下是一個小例子
gen = (i for i in range(10)) for i in iter(lambda: next(gen), 4): # 執行ntext(gen), 不斷返回生成器中的值,等於4則中止 print(i) >>> 0 1 2 3 the end...
yield能夠接收值
yield能夠接收send發送的值。以下代碼中,#16處send的值,會傳給#15中的yield,而後賦值給res。
def func(): res = yield 1 #15 print(res) f = func() f.send(None) # 預激 f.send(5) # 16
但願你們能夠經過本文掌握裝飾器這個殺手級特性。歡迎關注我的博客:藥少敏的博客