今天是二零一伍年八月二十五日
1、在python中有像list、tuple這樣的對象,他們是可迭代的(Iterable),這樣的對象在for語句中被循環。但是我們都知道,list是一個有限的對象。也就是說,其實當list被創建以後,他是具有長度的,每一個元素在內存中都是有位置的。但是有時候,在一個list裏面,我們有時候只需要前面幾個元素,這樣就造成了大量內存浪費。爲了防止這種浪費,我們就需要一種新的數據類型——生成器(generator)
顧名思義,生成器是動態生成內容的,比如:
l = [x * x for x in range(10)]
g = (x *x for x in range(10) )
把原來的方括號修改爲圓括號,就是我們的生成器了。g的列舉更加靈活:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
next()語句幫助我們一步一步的“計算”生成器中的值。生成器中保留的其實是一種算法和計數器,每次使用next時,就計算下一個元素的值。當然,在我們的算法裏也有一個上限,所以我們在訪問到最後就會有StopIteration的異常被拋出。當然我們的生成器也可以像其他可迭代對象一樣被for循環直接輸出。
但是有時候,生成一個有邏輯的序列卻並不是那麼簡單。比如斐波那契數列,我們們能僅僅用列表生成器就生成嗎?不行,這個時候必須使用函數:
# generator.py
def fib(max):
n , a , b = 0 , 0 , 1
while n < max :
print(b)
a , b = b , a + b
n += 1
return 'done'
那麼我們能用生成器生成一個動態斐波拉切嗎,答案是可以的。那麼我們就來分析一下斐波拉切函數,在這個函數裏,我們真正需要的其實是b這個變量,而print這個函數正好是輸出了這個變量。所以,想要把這個函數變成生成器,我們要從這裏入手,我們只用把打印語句改成yield b就行,也就是這樣:
# generator.py
def fib(max):
n , a , b = 0 , 0 , 1
while n < max :
yield b
a , b = b , a + b
n += 1
return 'done'
這個時候我們再嘗試去使用這個函數的時候就不一樣了。
g = fib(6)
此時的g就是一個生成器了,我們使用next或者是for循環就能打印出我們的斐波拉切數列。但是我們看回我們的定義,發現我們還有一個done被return了,那麼它到底什麼時候被返回呢?這個我想大家不用想也知道,它會在我們原先被拋出異常的時候返回,也就是:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: done
當然要是我們想正常輸出這個done的話,我們就需要捕獲異常了
2、剛剛在使用生成器的時候使用了Next句柄,那麼,是否還有其他的對象也可以使用next?答案是當然的,這樣的對象我們統一稱爲——迭代器(Iterator)。那麼我們的list、string是迭代器嗎?答案是否定的,關於這一點我們可以使用isinstance函數去檢驗。他們只是可迭代的(Iterable),遠不是迭代器。不過沒有關係我們可以使用iter()函數使它變成迭代器。
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
那麼爲什麼我們所鍾愛的這些數據類型不能成爲迭代器呢?因爲我們所稱的迭代器往往是一個數據流,它往往需要next函數去一個一個計算下一個值。有時候我們稱迭代器是惰性的,只有當我們需要下一個數據的時候他纔會計算。