本文講述了以下幾個方面:
1.何為迭代,何為可迭代對象,何為生成器,何為迭代器?
2.可迭代對象與迭代器之間的區別
3.生成器內部原理解析,for循環迭代內部原理解析
4.可迭代對象,迭代器,生成器,生成器函數之間關系
1.迭代
要搞清楚什么關於迭代器,生成器,可迭代對象,前提是我們要理解何為迭代。
第一,迭代需要重復進行某一操作
第二,本次迭代的要依賴上一次的結果繼續往下做,如果中途有任何停頓,都不能算是迭代.
下面來看看幾個例子,你就會更能理解迭代的含義。
# example1
# 非迭代 count = 0 while count < 10: print("hello world") count += 1
# example2
# 迭代 count = 0 while count < 10: print(count) count += 1
例子1,僅僅只是在重復一件事,那就是不停的打印"hello world",並且,這個打印的結果並不依賴上一次輸出的值。而例子2,就很好地說明迭代的含義,重復+繼續。
2.可迭代對象
按照上面迭代的含義,我們應該能夠知道何為可迭代對象。顧名思義,就是一個對象能夠被迭代的使用。那么我們該如何判斷一個對象是否可迭代呢?
Python提供了模塊collections,其中有一個isinstance(obj,string)的函數,可以判斷一個對象是否為可迭代對象。看下面實例:
from collections import Iterable f = open('a.txt') i = 1 s = '1234' d = {'abc':1} t = (1,2,344) m = {1,2,34,} print(isinstance(i, Iterable)) # 判斷整型是否為可迭代對象 print(isinstance(s, Iterable)) # 判斷字符串對象是否為可迭代對象 print(isinstance(d, Iterable)) # 判斷字典對象是否為可迭代對象 print(isinstance(t, Iterable)) # 判斷元組對象是否為可迭代對象 print(isinstance(m, Iterable)) # 判斷集合對象是否為可迭代對象 print(isinstance(f, Iterable)) # 判斷文件對象是否為可迭代對象 ########輸出結果######### False True True True True True
由上面得出,除了整型之外,python內的基本數據類型都是可迭代對象,包括文件對象。那么,python內部是如何知道一個對象是否為可迭代對象呢?答案是,在每一種數據類型對象中,都會有有一個__iter__()方法,正是因為這個方法,才使得這些基本數據類型變為可迭代。
如果不信,我們可以來看看下面代碼片段:

f = open('a.txt') i = 1 s = '1234' d = {'abc':1} t = (1,2,344) m = {1,2,34,} # hasattr(obj,string) 判斷對象中是否存在string方法 print(hasattr(i, '__iter__')) print(hasattr(s, '__iter__')) print(hasattr(d, '__iter__')) print(hasattr(t, '__iter__')) print(hasattr(m, '__iter__')) print(hasattr(f, '__iter__')) #########輸出結果####### C:\Python35\python3.exe D:/CODE_FILE/python/day21/迭代器.py False True True True True True
如果大家還是不信,可以繼續來測試。我們自己來寫一個類,看看有__iter__()方法和沒有此方法的區別。

# 沒有__iter__()方法 class Animal: def __init__(self): pass cat = Animal() print(isinstance(cat, Iterable)) ######輸出結果########## False # 有__iter__()方法 class Animal: def __init__(self): pass def __iter__(self): pass cat = Animal() print(isinstance(cat, Iterable)) ######輸出結果########## True
從上面,實驗結果可以看出一個對象是否可迭代,關鍵看這個對象是否有__iter__()方法。
3.迭代器
在介紹迭代器之前,我們先來了解一下容器這個概念。
容器是一種把多個元素組織在一起的數據結構,容器中的元素可以逐個地迭代獲取。簡單來說,就好比一個盒子,我們可以往里面存放數據,也可以從里面一個一個地取出數據。
在python中,屬於容器類型地有:list,dict,set,str,tuple.....。容器僅僅只是用來存放數據的,我們平常看到的 l = [1,2,3,4]等等,好像我們可以直接從列表這個容器中取出元素,但事實上容器並不提供這種能力,而是可迭代對象賦予了容器這種能力。
說完了容器,我們在來談談迭代器。迭代器與可迭代對象區別在於:__next__()方法。
我們可以采用以下方法來驗證一下:

from collections import Iterator f = open('a.txt') i = 1 s = '1234' d = {'abc':1} t = (1, 2, 344) m = {1, 2, 34, } print(isinstance(i,Iterator)) print(isinstance(s,Iterator)) print(isinstance(d,Iterator)) print(isinstance(t,Iterator)) print(isinstance(m,Iterator)) print(isinstance(f,Iterator)) ########輸出結果########## False False False False False True
結果顯示:除了文件對象為迭代器,其余均不是迭代器
下面,我們進一步來驗證一下:

print(hasattr(i,"__next__")) print(hasattr(s,"__next__")) print(hasattr(d,"__next__")) print(hasattr(t,"__next__")) print(hasattr(m,"__next__")) print(hasattr(f,"__next__")) #######結果########### False False False False False True
從輸出結果可以表明,迭代器與可迭代對象僅僅就是__next__()方法的有無。
4.for內部機制剖析
先來看看一段普通的迭代過程:
l = [1,2,3,4,5] for i in l: print(i)
根據之前的分析,我們知道 l = [1,2,3,4,5]是一個可迭代對象。而且可迭代對象是不可以直接從其中取到元素。那么為啥我們還能從列表L中取到元素呢?這一切都是因為for循環內部實現。在for循環內部,首先L會調用__iter__()方法,將列表L變為一個迭代器,然后這個迭代器再調用其__next__()方法,返回取到的第一個值,這個元素就被賦值給了i,接着就打印輸出了。
下面,我們通過一系列的實驗來證明上述所說的。

l = [1,2,3,4,5,6] item = l.__iter__() # 將l變為迭代器 print(item.__next__()) # 迭代器調用next方法,並且返回取出的元素 print(item.__next__()) print(item.__next__()) print(item.__next__()) print(item.__next__()) print(item.__next__()) print(item.__next__()) # 報錯 #######輸出結果############# 1 2 3 4 5 6 ######上面為什么報錯呢??########## #當調用了最后一個next方法,沒有下一個元素可取 #就會報錯StopIteration異常錯誤。你可能會想會 #為什么for循環沒有報錯?答案很簡單,因為for循 #環內部幫我們捕捉到了這個異常,一旦捕捉到異常 #說明,迭代應該結束了! ###########################
上述實驗,與我上面說明的一致。
下面,我們可以while循環來模擬for循環,輸出列表中的元素。
l = [1,2,3,4,5] item = l.__iter__() # 生成一個迭代器 while True: try: i = item.__next__() print(i) except StopIteration: # 捕獲異常,如果有異常,說明應該停止迭代 break
由上分析,我們可以總結出:當我們試圖用for循環來迭代一個可迭代對象時候,for循環在內部進行了兩步操作:第一,將可迭代對象S變為迭代器M;第二,迭代器M調用__next__()方法,並且返回其取出的元素給變量i。
你可能看見過這種寫法,for i in iter(M):xxx ,其實這一步操作和我們上面沒什么區別。iter()函數,就是將一個可迭代對象M變為迭代器也就是M調用__iter__()方法,然后內部在調用__next__()方法。也就是說,
M = [1,2,3,4,5] for i in iter(M): # 等價於 M.__iter()__ 人為顯示調用 print(i) for i in M: # 解釋器隱式調用 print(i) ################# # #上面輸出的結果完全一樣 # #################
還有next(M)等價於M.__next__。
迭代器優點:
1.節約內存
2.不依賴索引取值
3.實現惰性計算(什么時候需要,在取值出來計算)
5.生成器(本質就是迭代器)
什么是生成器?可以理解為一種數據類型,這種數據類型自動實現了迭代器協議(其他的數據類型需要調用自己內置的__iter__方法)。
按照我們之前所說的,迭代器必須滿足兩個條件:既有__iter__(),又有__next__()方法。那么生成器是否也有這兩個方法呢?答案是,YES。具體來通過以下代碼來看看。

def func(): print("one------------->") yield 1 print("two------------->") yield 2 print("three----------->") yield 3 print("four------------>") yield 4 print(hasattr(func(),'__next__')) print(hasattr(func(),'__iter__')) #########輸出結果########### True True
實驗表明,生成器就是迭代器。
Python有兩種不同的方式提供生成器:
1.生成器函數(函數內部有yield關鍵字):常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行
2.生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表
既然生成器就是迭代器,那么我們是不是也可以通過for循環來遍歷出生成器中的內容呢?看下面代碼.

def func(): print("one------------->") yield 1 print("two------------->") yield 2 print("three----------->") yield 3 print("four------------>") yield 4 for i in func(): print(i) #########輸出結果######## one-------------> 1 two-------------> 2 three-----------> 3 four------------> 4
很顯然,生成器也可以通過for循環來遍歷出其中的內容。
下面我們來看看生成器函數執行流程:

def func(): print("one------------->") yield 1 print("two------------->") yield 2 print("three----------->") yield 3 print("four------------>") yield 4 g = func() # 生成器 == 迭代器 print(g.__next__()) print(g.__next__()) print(g.__next__()) print(g.__next__())
每次調用g.__next__()就回去函數內部找yield關鍵字,如果找得到就輸出yield后面的值並且返回;如果沒有找到,就會報出異常。上述代碼中如果在調用g.__next__()就會報錯。
Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。
實例:生成器模擬Linux下tail -f a.txt | grep 'error' | grep '404'

import time def tail(filepath): with open(filepath, encoding='utf-8') as f: f.seek(0, 2) # 停到末尾開頭 1從當前位置 2從文件末尾 while True: line = f.readline() if line: # 如果有內容讀出 #print(line,end='') yield line # 遍歷時停在此行,並且將其返回值傳遞出去 else: time.sleep(0.5) # 如果文件為空,休眠 等待輸入 def grep(lines, patterns): # lines為生成器類型 for line in lines: # 遍歷生成器 if patterns in line: yield line g = grep(tail('a.txt'), 'error') # 動態跟蹤文件新添加的內容,並且過濾出有patterns的行 g1 = grep(g,'404') # g1為生成器 for i in g1: # 通過for循環來隱式調用__next__()方法 print(i)
生成器小結:
1.是可迭代對象
2.實現了延遲計算,省內存啊
3.生成器本質和其他的數據類型一樣,都是實現了迭代器協議,只不過生成器附加了一個延遲計算省內存的好處,其余的可迭代對象可沒有這點好處!
6.可迭代對象、迭代器、生成器關系總結