1. 程式人生 > >Python基礎 迭代器與生成器

Python基礎 迭代器與生成器

Python基礎 迭代器與生成器

迭代器 生成器

迭代器

可迭代物件(iterable)

但凡是可以返回一個迭代器的物件都可稱之為可迭代物件,看個例子

  1. >>> x = [1, 2, 3] 
  2. >>> y = iter(x) 
  3. >>> z = iter(x) 
  4. >>> next(y) 

  5. >>> next(y) 

  6. >>> next(z) 

  7. >>> type(x) 
  8. <class 'list'> 
  9. >>> type(y) 
  10. <class 'list_iterator'> 

這裡x是一個可迭代物件,這只是一種通俗的叫法,並不是一種資料型別.
y和z是兩個獨立的迭代器,迭代器內部持有一個狀態,該狀態用於記錄當前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。

迭代器(iterator)

那什麼是迭代器呢?它是一個帶狀態的的物件,他能在你呼叫next()方法的時候返回容器中的下一個值,任何實現了__iter__() 和 __ next__() 方法的物件都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一個值,如果容器中沒有更多元素了,則丟擲StopIteration異常.

例子

  1. # 生成無限序列 
  2. >>> from itertools import count 
  3. >>> counter = count(start=13) 
  4. >>> next(counter) 
  5. 13 
  6. >>> next(counter) 
  7. 14 
  1. # 從有限序列中生成無限序列 
  2. >>> from itertools import count 
  3. >>> counter = count(start=13) 
  4. >>> next(counter) 
  5. 13 
  6. >>> next(counter) 
  7. 14 
  1. # 為了更直觀地感受迭代器內部的執行過程,我們自定義一個迭代器,以斐波那契數列為例 
  2. class Fib: 
  3. def __init__(self): 
  4. self.prev = 0 
  5. self.curr = 1 
  6.  
  7. def __iter__(self): 
  8. return self 
  9.  
  10. def __next__(self): 
  11. value = self.curr 
  12. self.curr += self.prev 
  13. self.prev = value 
  14. return value 
  15.  
  16. >>> f = Fib() 
  17. >>> list(islice(f, 0, 10)) 
  18. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 

Fib既是一個可迭代物件(因為它實現了__iter__方法),又是一個迭代器(因為實現了__next__方法)。例項變數prev和curr使用者維護迭代器內部的狀態。每次呼叫next()方法的時候做兩件事:

1.為下一次呼叫next()方法修改狀態
2 為當前這次呼叫生成返回結果

生成器

什麼是生成器?

  通過列表生成式,我們可以直接建立一個列表,但是,受到記憶體限制,列表容量肯定是有限的,而且建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。

  所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?這樣就不必建立完整的list,從而節省大量的空間,在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator

  生成器是一個特殊的程式,可以被用作控制迴圈的迭代行為,python中生成器是迭代器的一種,使用yield返回值函式,每次呼叫yield會暫停,而可以使用next()函式和send()函式恢復生成器。

  生成器類似於返回值為陣列的一個函式,這個函式可以接受引數,可以被呼叫,但是,不同於一般的函式會一次性返回包括了所有數值的陣列,生成器一次只能產生一個值,這樣消耗的記憶體數量將大大減小,而且允許呼叫函式可以很快的處理前幾個返回值,因此生成器看起來像是一個函式,但是表現得卻像是迭代器(實際就是迭代器)

python中的生成器

  要建立一個generator,有很多種方法,第一種方法很簡單,只有把一個列表生成式的[]中括號改為()小括號,就建立一個generator

  1. #列表生成式 
  2. lis = [x*x for x in range(10)] 
  3. print(lis) 
  4. #生成器 
  5. generator_ex = (x*x for x in range(10)) 
  6. print(generator_ex) 
  7.  
  8. 結果: 
  9. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 
  10. <generator object <genexpr> at 0x000002A4CBF9EBA0> 

那麼建立lis和generator_ex,的區別是什麼呢?從表面看就是[ ]和(),但是結果卻不一樣,一個打印出來是列表(因為是列表生成式),而第二個打印出來卻是<generator object

  如果要一個個打印出來,可以通過next()函式獲得generator的下一個返回值:

  1.  
  2. #生成器 
  3. generator_ex = (x*x for x in range(10)) 
  4. print(next(generator_ex)) 
  5. print(next(generator_ex)) 
  6. print(next(generator_ex)) 
  7. print(next(generator_ex)) 
  8. print(next(generator_ex)) 
  9. print(next(generator_ex)) 
  10. print(next(generator_ex)) 
  11. print(next(generator_ex)) 
  12. print(next(generator_ex)) 
  13. print(next(generator_ex)) 
  14. print(next(generator_ex)) 
  15. 結果: 




  16. 16 
  17. 25 
  18. 36 
  19. 49 
  20. 64 
  21. 81 
  22. Traceback (most recent call last): 
  23.  
  24. File "列表生成式.py", line 42, in <module> 
  25.  
  26. print(next(generator_ex)) 
  27.  
  28. StopIteration 

  大家可以看到,generator儲存的是演算法,每次呼叫next(generaotr_ex)就計算出他的下一個元素的值,直到計算出最後一個元素,沒有更多的元素時,丟擲StopIteration的錯誤,而且上面這樣不斷呼叫是一個不好的習慣,正確的方法是使用for迴圈,因為generator也是可迭代物件:

  1. #生成器 
  2. generator_ex = (x*x for x in range(10)) 
  3. for i in generator_ex: 
  4. print(i) 
  5.  
  6. 結果: 




  7. 16 
  8. 25 
  9. 36 
  10. 49 
  11. 64 
  12. 81 

  所以我們建立一個generator後,基本上永遠不會呼叫next(),而是通過for迴圈來迭代,並且不需要關心StopIteration的錯誤,generator非常強大,如果推算的演算法比較複雜,用類似列表生成式的for迴圈無法實現的時候,還可以用函式來實現。

  這裡說一下generator和函式的執行流程,函式是順序執行的,遇到return語句或者最後一行函式語句就返回。而變成generator的函式,在每次呼叫next()的時候執行,遇到yield語句返回,再次被next()呼叫時候從上次的返回yield語句處急需執行,也就是用多少,取多少,不佔記憶體。