Python迭代器,生成器分享

Py西遊攻關之迭代器&生成器

一 概要

在瞭解Python的數據結構時,容器(container)、可迭代對象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推導式(list,set,dict comprehension)衆多概念參雜在一塊兒,不免讓初學者一頭霧水,我將用一篇文章試圖將這些概念以及它們之間的關係捋清楚python

圖片

 

二 容器(container)

容器是一種把多個元素組織在一塊兒的數據結構,容器中的元素能夠逐個地迭代獲取,能夠用 in , not in 關鍵字判斷元素是否包含在容器中。一般這類數據結構把全部的元素存儲在內存中(也有一些特列並非全部的元素都放在內存)在Python中,常見的容器對象有:數據結構

  • list, deque, ....app

  • set, frozensets, ....socket

  • dict, defaultdict, OrderedDict, Counter, ....ide

  • tuple, namedtuple, …函數

  • str學習

容器比較容易理解,由於你就能夠把它看做是一個盒子、一棟房子、一個櫃子,裏面能夠塞任何東西。從技術角度來講,當它能夠用來詢問某個元素是否包含在其中時,那麼這個對象就能夠認爲是一個容器,好比 list,set,tuples都是容器對象:spa

圖片

儘管絕大多數容器都提供了某種方式來獲取其中的每個元素,但這並非容器自己提供的能力,而是 可迭代對象 賦予了容器這種能力,固然並非全部的容器都是可迭代的。code

 

三 可迭代對象(iterable)

若是給定一個list或tuple,咱們能夠經過for循環來遍歷這個list或tuple,這種遍歷咱們稱爲迭代(Iteration)。orm

剛纔說過,不少容器都是可迭代對象,此外還有更多的對象一樣也是可迭代對象,好比處於打開狀態的files,sockets等等。但凡是能夠返回一個 迭代器 的對象均可稱之爲可迭代對象,聽起來可能有點困惑,不要緊,可迭代對象與迭代器有一個很是重要的區別。先看一個例子:

圖片

這裏 x 是一個可迭代對象,可迭代對象和容器同樣是一種通俗的叫法,並非指某種具體的數據類型,list是可迭代對象,dict是可迭代對象,set也是可迭代對象。 y 和 z 是兩個獨立的迭代器,迭代器內部持有一個狀態,該狀態用於記錄當前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。迭代器有一種具體的迭代器類型,好比 list_iterator , set_iterator 。可迭代對象實現了 __iter__ 和 __next__ 方法(python2中是 next 方法,python3是 __next__ 方法),這兩個方法對應內置函數 iter() 和 next() 。 __iter__ 方法返回可迭代對象自己,這使得他既是一個可迭代對象同時也是一個迭代器。

四 迭代器(iterator)

那麼什麼迭代器呢?它是一個帶狀態的對象,他能在你調用 next() 方法的時候返回容器中的下一個值,任何實現了 __next__() (python2中實現 next() )方法的對象都是迭代器,至於它是如何實現的這並不重要。

如今咱們就以斐波那契數列()爲例,學習爲什麼建立以及如何建立一個迭代器:

著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數均可由前兩個數相加獲得:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

def fab(max):

= 0, 0, 1 
     n <= b, a += n + 1

直接在函數fab(max)中用print打印會致使函數的可複用性變差,由於fab返回None。其餘函數沒法得到fab函數返回的數列。

== 0, 0, 1 
     n <= b, a += n + 1
     L

代碼2知足了可複用性的需求,可是佔用了內存空間,最好不要。

對比for i in range(1000): pass和for i in xrange(1000): pass,前一個返回1000個元素的列表,然後一個在每次迭代中返回一個元素,所以可使用迭代器來解決複用可佔空間的問題


 == 0, 0, 1 

      self.n <== self.b, self.a += self.n + 1

Fabs 類經過 next() 不斷返回數列的下一個數,內存佔用始終爲常數

Fib既是一個可迭代對象(由於它實現了 __iter__ 方法),又是一個迭代器(由於實現了 __next__ 方法)。實例變量 self .a 和 self.b 用戶維護迭代器內部的狀態。每次調用 next() 方法的時候作兩件事:

  1. 爲下一次調用 next() 方法修改狀態

  2. 爲當前此次調用生成返回結果

迭代器就像一個懶加載的工廠,等到有人須要的時候纔給它生成值返回,沒調用的時候就處於休眠狀態等待下一次調用。

五 for i in (iterable)的內部實現

在大多數狀況下,咱們不會一次次調用next方法去取值,而是經過 for i in (iterable),

       

注意:in後面的對象若是是一個迭代器,內部由於有iter方法才能夠進行操做,因此,迭代器協議裏面有iter和next兩個方法,不然for語句沒法應用。

 

注意:

 

1

2

for in range10):

        print i     :定時垃圾回收機制:沒有引用指向這個對象,則被回收

 

六 生成器(generator)

      生成器算得上是Python語言中最吸引人的特性之一,生成器實際上是一種特殊的迭代器,不過這種迭代器更加優雅。代碼3遠沒有代碼1簡潔,生成器(yield)既能夠保持代碼1的簡潔性,又能夠保持代碼3的效果。它不須要再像上面的類同樣寫 __iter__() 和 __next__() 方法了,只須要一個 yiled 關鍵字。 生成器有以下特徵是它必定也是迭代器(反之不成立),所以任何生成器也是以一種懶加載的模式生成值。用生成器來實現斐波那契數列的例子是:

= 0, 0, 1
     n <= b, a += n + 1


>>>  n  fab(51
1
2
3
5


     fib 就是一個普通的python函數,它特需的地方在於函數體中沒有 return 關鍵字,函數的返回值是一個生成器對象。當執行 f=fib(5) 返回的是一個生成器對象,此時函數體中的代碼並不會執行,只有顯示或隱示地調用next的時候纔會真正執行裏面的代碼。

     yield 的做用就是把一個函數變成一個 generator,帶有 yield 的函數再也不是一個普通函數,Python 解釋器會將其視爲一個 generator,在 for 循環執行時,每次循環都會執行 fab 函數內部的代碼,執行到 yield b 時,fab 函數就返回一個迭代值,下次迭代時,代碼從 yield b 的下一條語句繼續執行,而函數的本地變量看起來和上次中斷執行前是徹底同樣的,因而函數繼續執行,直到再次遇到 yield。看起來就好像一個函數在正常執行的過程當中被 yield 中斷了數次,每次中斷都會經過 yield 返回當前的迭代值。

也能夠手動調用 fab(5) 的 next() 方法(由於 fab(5) 是一個 generator 對象,該對象具備 next() 方法),這樣咱們就能夠更清楚地看到 fab 的執行流程:


>>> f = fab(3>>> f.1
>>> f.1
>>> f.2
>>> f., line 1,  <module>


 

須要明確的就是生成器也是iterator迭代器,由於它遵循了迭代器協議.

兩種建立方式

包含yield的函數

生成器函數跟普通函數只有一點不同,就是把 return 換成yield,其中yield是一個語法糖,內部實現了迭代器協議,同時保持狀態能夠掛起。以下:

return:

在一個生成器中,若是沒有return,則默認執行到函數完畢;若是遇到return,若是在執行過程當中 return,則直接拋出 StopIteration 終止迭代.


def f():

   
yield 5
   print("ooo")
   
return
   yield 6
   print("ppp")
       
# if str(tem)=='None':
       #     print("ok")

f
=f()
# print(f.__next__())
#
print(f.__next__())
for i in f:
   
print(i)

'''
return即迭代結束
for不報錯的緣由是內部處理了迭代結束的這種狀況
'''


注意:

文件讀取


= 1024=


若是直接對文件對象調用 read() 方法,會致使不可預測的內存佔用。好的方法是利用固定長度的緩衝區來不斷讀取文件內容。經過 yield,咱們再也不須要編寫讀文件的迭代類,就能夠輕鬆實現文件讀取。

 

My:生成器對象就是一種特殊的迭代器對象,知足迭代器協議,能夠調用next;對生成器對象for 循環時,調用iter方法返回了生成器對象,而後再不斷next迭代,而iter和next都是在yield內部實現的。

練習1:使用文件讀取,找出文件中最長的行的?

max(len(x.strip()) for x inopen('/hello/abc','r'))

練習2:


 s + i  range(4= n  [1, 10= (add(i, n)  i  list(base)


'''
核心語句就是:
for n in [1, 10]:
base = (add(i, n) for i in base)
在執行list(base)的時候,開始檢索,而後生成器開始運算了。關鍵是,這個循環次數是2,也就是說,有兩次生成器表達
式的過程。必須緊緊把握住這一點。
生成器返回去開始運算,n = 10而不是1沒問題吧,這個在上面提到的文章中已經提到了,就是add(i, n)綁定的是n這個
變量,而不是它當時的數值。
而後首先是第一次生成器表達式的執行過程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),這是第一次循環的結
果(形象表示,其實已經計算出來了(10,11,12,3)),而後第二次,
base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,終於獲得結果了[20, 21, 22, 23].
'''


練習3:自定義range

圖片

七 生成器的擴展

生成器對象支持幾個方法,如gen.next() ,gen.send() ,gen.throw()等。

 因爲沒有額外的yield,因此將直接拋出StopIteration。

send的工做方式:


(= 7
     8=

協程應用:

      所謂協同程序也就是是能夠掛起,恢復,有多個進入點。其實說白了,也就是說多個函數能夠同時進行,能夠相互之間發送消息等。


 x  range(4 (+

 x  range(4 (+

 =  i =
                =
相關文章
相關標籤/搜索