Python基礎 迭代器與生成器
Python基礎 迭代器與生成器
迭代器 生成器迭代器
可迭代物件(iterable)
但凡是可以返回一個迭代器的物件都可稱之為可迭代物件,看個例子
- >>> x = [1, 2, 3]
- >>> y = iter(x)
- >>> z = iter(x)
- >>> next(y)
- 1
- >>> next(y)
- 2
- >>> next(z)
- 1
- >>> type(x)
- <class 'list'>
- >>> type(y)
- <class 'list_iterator'>
這裡x是一個可迭代物件,這只是一種通俗的叫法,並不是一種資料型別.
y和z是兩個獨立的迭代器,迭代器內部持有一個狀態,該狀態用於記錄當前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。
迭代器(iterator)
那什麼是迭代器呢?它是一個帶狀態的的物件,他能在你呼叫next()方法的時候返回容器中的下一個值,任何實現了__iter__() 和 __ next__() 方法的物件都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一個值,如果容器中沒有更多元素了,則丟擲StopIteration異常.
例子
- # 生成無限序列
- >>> from itertools import count
- >>> counter = count(start=13)
- >>> next(counter)
- 13
- >>> next(counter)
- 14
- # 從有限序列中生成無限序列
- >>> from itertools import count
- >>> counter = count(start=13)
- >>> next(counter)
- 13
- >>> next(counter)
- 14
- # 為了更直觀地感受迭代器內部的執行過程,我們自定義一個迭代器,以斐波那契數列為例
- class Fib:
- def __init__(self):
- self.prev = 0
- self.curr = 1
-
- def __iter__(self):
- return self
-
- def __next__(self):
- value = self.curr
- self.curr += self.prev
- self.prev = value
- return value
-
- >>> f = Fib()
- >>> list(islice(f, 0, 10))
- [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
- #列表生成式
- lis = [x*x for x in range(10)]
- print(lis)
- #生成器
- generator_ex = (x*x for x in range(10))
- print(generator_ex)
-
- 結果:
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- <generator object <genexpr> at 0x000002A4CBF9EBA0>
那麼建立lis和generator_ex,的區別是什麼呢?從表面看就是[ ]和(),但是結果卻不一樣,一個打印出來是列表(因為是列表生成式),而第二個打印出來卻是<generator object
如果要一個個打印出來,可以通過next()函式獲得generator的下一個返回值:
-
- #生成器
- generator_ex = (x*x for x in range(10))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- 結果:
- 0
- 1
- 4
- 9
- 16
- 25
- 36
- 49
- 64
- 81
- Traceback (most recent call last):
-
- File "列表生成式.py", line 42, in <module>
-
- print(next(generator_ex))
-
- StopIteration
大家可以看到,generator儲存的是演算法,每次呼叫next(generaotr_ex)就計算出他的下一個元素的值,直到計算出最後一個元素,沒有更多的元素時,丟擲StopIteration的錯誤,而且上面這樣不斷呼叫是一個不好的習慣,正確的方法是使用for迴圈,因為generator也是可迭代物件:
- #生成器
- generator_ex = (x*x for x in range(10))
- for i in generator_ex:
- print(i)
-
- 結果:
- 0
- 1
- 4
- 9
- 16
- 25
- 36
- 49
- 64
- 81
所以我們建立一個generator後,基本上永遠不會呼叫next(),而是通過for迴圈來迭代,並且不需要關心StopIteration的錯誤,generator非常強大,如果推算的演算法比較複雜,用類似列表生成式的for迴圈無法實現的時候,還可以用函式來實現。
這裡說一下generator和函式的執行流程,函式是順序執行的,遇到return語句或者最後一行函式語句就返回。而變成generator的函式,在每次呼叫next()的時候執行,遇到yield語句返回,再次被next()呼叫時候從上次的返回yield語句處急需執行,也就是用多少,取多少,不佔記憶體。