python---基礎知識回顧(七)叠代器和生成器
前戲:叠代器和生成器
叠代:
如果給定一個list或tuple,我們可以通過for循環來遍歷這個list或tuple,這種遍歷我們稱為叠代(Iteration)。
Python的for循環不僅可以用在list或tuple上,還可以作用在其他可叠代對象上(用isinstance判斷)
可以直接作用於for循環的對象統稱為可叠代對象
可以直接作用於for循環的數據類型有以下幾種:
一類是集合數據類型,如list、tuple、dict、set、str等;(稱為容器<容器是一種把多個元素組織在一起的數據結構>,很多容器都是可叠代的)
一類是generator,包括生成器和帶yield的generator function。
這些可以直接作用於for循環的對象統稱為可叠代對象:Iterable。
(一)叠代器
一個實現了__iter__方法的對象是可叠代的,一個實現了__next__方法的對象則是叠代器
對於序列和字典的可叠代,是因為在該對象中實現了上面的兩個方法
__iter__方法會返回一個叠代器,而所謂的叠代器就是具有__next__方法的對象。在調用__next__方法時,叠代器會返回他的下一個值。若是next方法被調用牡丹石叠代器中沒有值可以返回,就會引發一個StopIteration異常
叠代器的優點:需要數據就去獲取,而不是一次獲取全部數據
相對於我們一次性取出數據,放在列表等類型中,若數據量過大,那麽列表會占據大量的內存。而且對於這些數據,我們若是只使用一次就釋放的話,那麽放在列表中實在是太過浪費內存。
更好的方法就是使用叠代器。叠代器只會取出當前需要的數據方法內存中。
例如Django中的queryset惰性機制中就有提及叠代器的好處(在處理大量的數據時)
栗子:不使用列表的案例,因為如果使用列表,那麽列表長度將會是無窮大。占據空間將會是巨大的。
斐波那契數列:
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 f = Fibs() for i in f: if i > 1000: print(i) #1597 break
補充:內建函數iter可以從可叠代的對象中獲取叠代器
>>> a = [1,2,3,] >>> b = iter(a) >>> type(b) <class ‘list_iterator‘> >>> next(b) 1 >>> next(b) 2 >>> next(b) 3 >>> next(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
可以知道叠代器是一次性消耗品(只會向前獲取,不會向後獲取),當耗盡時就會觸發StopIteration異常
若是想保留一份數據,可以用deepcopy
從叠代器中獲取序列:
class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a,self.b = self.b,self.a+self.b if self.a > 1597: raise StopIteration return self.a def __iter__(self): return self f = Fibs() ls = list(f) print(ls) #[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
使用list構造方法顯示的將叠代器轉換為列表
class list(object): def __init__(self, seq=()): # known special case of list.__init__ """ list() -> new empty list list(iterable) -> new list initialized from iterable‘s items
(若是叠代器,那麽新的列表則是叠代器的所有成員,結束是以StopIteration為標誌,若是上面沒有觸發,那麽會一直去擴展列表) # (copied from class doc) """ pass
結束
應該還記得列表推導式(生成式)<順道回憶下lambda表達式>
>>> [x for x in range(100) if x % 7 == 0] [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]
通過列表推導式,我們可以直接生成一個列表,同樣的,這個列表的內存也是受到限制的,當我們使用列表推導式,一次生成一個超大數量的列表,會占據大量內存,然而,若我們只是訪問了前面幾個,那麽後面的空間占用幾乎是無用的。
for i in [x for x in range(100) if x % 7 == 0]: if i < 50: print(i) else: break
在上面案例中,我們只是想去獲取滿足條件的數據的一部分。但是在進行循環時,並不會立刻進行,而是需要將列表生成式全部執行後,才允許去進行循環。而我們所需要的數據僅僅是列表中的前一部分,但是列表推導式一次性將數據全部生成。占據大量無用的空間。那麽我們是否可以做到像叠代器那樣,需要的時候再去獲取。從而避免數據冗余
(二)生成器
生成器都是叠代器。生成器是一種用普通函數語法定義的叠代器
創建一個生成器方法有多種:
其中第一種與列表推導式十分相似,只是需要將中括號[]變為小括號()
>>> b = [x for x in range(100) if x % 7 == 0] >>> type(b) <class ‘list‘> [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98] >>> b = (x for x in range(100) if x % 7 == 0) >>> type(b) <class ‘generator‘> >>> next(b) 0 >>> next(b) 7 >>> next(b) 14 >>> for i in b: ... print(i) 當數據全部取出後也會觸發StopIteration錯誤
另外一種是:任何包括yield語句的函數都可以稱為生成器。
這裏同樣以斐波那契數列為例:
def fibs(max): n,a,b = 0,0,1 while n < max: yield a #yield語句 n += 1 a, b = b, a+b f = fibs(8) for i in f: print(i) #0 1 1 2 3 5 8 13
生成器和普通函數的行為有很大的區別。
- 不像return返回一次結果就結束函數,而是可以返回多次結果。從什麽for循環可以看出,這一個函數返回了不止一次結果
- 每產生一個值(即在yield語句中返回的值),函數就會被凍結,不在執行:即函數停在那點等待被重新喚醒。被重新喚醒後就從之前通知的那點開始執行
def 函數: ... yield 1 執行第一次後返回值1後凍結,不在執行,等待第二次 ... #執行第二次時會向下繼續執行,直到下一次yield ... ... yield 2 #第二次凍結(這個過程包括了執行上面的邏輯語句) ... ... ... yield 3
案例:
>>> def flatten(nested): ... for sublist in nested: ... for ele in sublist: ... yield ele >>> for num in flatten(nested): ... print(num) ... 1 2 3 4 5 >>>
也可以同上面叠代器一樣使用list顯示轉換為列表。
>>> list(flatten(nested)) [1, 2, 3, 4, 5] >>>
但是這樣會立刻實例化列表,喪失了叠代的優勢。
def nrange(num): temp = -1 while True: temp = temp + 1 if temp >= num: return else: yield temp for i in nrange(10): print(i)含有return的自定義nrange生成器
通用生成器:
生成器是一個包含yield關鍵字的函數。當他被調用的時候,在函數體中的代碼不會執行,而是會返回一個叠代器。每次請求一個值,就會執行生成器中的代碼,直到遇到一個yield或者return語句。(yield意味著生成一個值,並凍結執行,等待下一次執行。return意味著生成器要通知執行)
生成器由兩部分組成:生成器的函數和生成器的叠代器。生成器的函數使用def語句定義的,包含yield的。生成器的叠代器是這個函數的返回部分。合在一起就是生成器。
生成器方法:
生成器中新特征:可以為生成器提供值,而不是只像上面那樣生成器為外面返回值。生成器內外可以進行交流
外部作用域訪問生成器的send方法,可以向生成器內部傳遞消息(任意對象),此時yield不再是一個返回值語句,而是一個表達式
send方法和yield語句的執行區別:
yield
def rep2(val): yield val print("aaa") val += 10 print("bbb") yield val r = rep2(33) print(next(r)) #可以看出,執行yield返回值後,就凍結在該條語句,不再向下執行,只有當下一個next出現,才會繼續執行
def rep(val):new = (yield val) #接收send發送過來的數據 if new: print(new) new = (yield val) if new: print(new) f = rep(33) v = next(f) f.send("dsad") #會打印出來dasd 可以看出,當send發送數據後,在接收數據後,會繼續向下執行,直到下一個yield表達式出現
send方法使用:
註意:在使用send方法時,只有當生成器掛起以後才有意義(也就是說:在yield函數第一次執行之後)
def rep(val): new = (yield val) if new: print(new) new = (yield val) if new: print(new)
開始執行:
若沒有將生成器掛起:
TypeError: can‘t send non-None value to a just-started generator
所以,我們在使用時需要先掛起生成器。掛起方法有兩種:
第一種:
r = rep(33) v = next(r) #這裏正常執行next獲取yield返回,後面就可以正常使用send print(v) r.send("dsad") r.send("dsadds")
第二種(由剛剛的TypeError可以知道不能send一個非None值,在第一次時,所以我們可以直接在第一次時send(None)):
r.send(None) r.send("dsad") r.send("dsadds")
兩種方法,強烈推薦第二種
原因:使用send方法時,需要註意兩點
1.需要先將生成器掛起,此時才有意義
2.send方法第一次使用時,也是需要進行一次next()方法執行,或者send(None)執行
第2條件是在我們使用send前有其他yield語句返回時,可以了解到
def rep(val): yield val new = (yield val) if new: print(new) new = (yield val) if new: print(new) r = rep(33) v = next(r) #掛起生成器 print(v) v = next(r) #激活send方法 print(v) r.send("dsad") r.send("dsadds")
---------------- 正常輸出 #33 #33 #dsad #dsadds
若是只是掛起了生成器,沒有激活send方法,那麽默認第一個send方法會拿去激活
r = rep(33) v = next(r) print(v) # v = next(r) #沒有去激活send方法 # print(v) r.send("dsad") #第一個send方法會被用到去激活send r.send("dsadds") #這個才是正常的信息傳入
----------
#33
#dsadds
所以我們最好使用send(None)表示去激活send方法,不易混淆
def rep(val): yield val new = (yield val) if new: print(new) new = (yield val) if new: print(new) r = rep(33) v = next(r) #掛起生成器 也可以用send(None)去掛起生成器,但是還是不要這樣做,兩個套用容易混淆 print(v) r.send(None) #激活send方法(在首次使用send時使用) r.send("dsad") r.send("dsadds")
--------------
#33
#dsad
#dsadds
另外補充下send方法會獲取到yield表達式中的返回值
def rep(val): yield val new = (yield val) if new: print(new) new = (yield val) if new: print(new) r = rep(33) v = next(r) print(v) v = r.send(None) print(v,1) v = r.send("dsad") print(v,2) v = r.send("dsadds") #在最後一個send方法時,沒有返回值 print(v,3) ----------------------- 33 33 1 dsad 33 2 dsadds
send方法會依次獲取yield表達式的返回值,所以在第三個send方法使用時,並沒有yield與之對應,所以沒有值。
具體原因暫不討論。
註意區分yield語句和yield表達式
python---基礎知識回顧(七)叠代器和生成器