Generator設計的目的是為了利用推導功能,一步步算出下一個元素是多少,著眼點是list如果一次產生多個元素,但卻只要前面幾個元素,就會很浪費空間。假定產生20個元素
ex:L=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
只要偶數的話呢?可以像這樣
ex:L=[ x for x in range(20) if x%2==0]
print(L) #[0,2,4,6,8,10,12,14,16,18]
但請注意,還是建立了一個完整list,只是都是偶數,如果只要『需要的時候才去推算出下一個元素』,這樣的前提下可以減少空間的浪費,於是,使用Generator
Generator跟list的差別就是,list生成的時候空間已經佔據,Generator則是『推算』出來才會佔據空間,而且佔據的空間為一個常數,Generator跟list很像,差別在於list使用[],Generator使用() ,其他都一樣
ex: g=( x for x in range(20) if x%2==0)
print(g) #< generator object <genexpr> at 0x7fddebb01150>
如同前面提到過,generator是『計算』或者『推算』產生下一個元素,因此,沒有下cmd時候,g根本不會產生,去印只會印出 堆看不懂的玩意,那,怎麼印出元素呢
使用 next(g)
ex : g=( x for x in range(10) )
next(g) # 0
next(g) # 1
next(g) # 2
next(g) # 3
next(g) # 4
next(g) # 5
next(g) # 6
next(g) # 7
next(g) # 8
next(g) # 9
如果range(100) ?
其實generator一樣可以使用for來印出元素
ex : g=( x for x in range(10) )
for n in g:
print(n) #0 1 2 3 4 5 6 7 8 9
要注意一點,如果你用next(g)一路送到最後出現
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
此時,next(g)已經無法繼續送出下一個元素,因為所有的元素都被送完
繼續next(g)一樣會出線
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
妙的地方在於,這時候在用for去送代,會根本無法印出任何東西
for n in g:
print(n) #(nothing)
------------------------------------
同樣的,假設一開始就用for去印
ex : g=( x for x in range(10) )
for n in g:
print(n) #0 1 2 3 4 5 6 7 8 9
之後在用for印呢?
for n in g:
print(n) #(nothing)
對,一樣什麼都沒有印出來
#以下為個人推論
generator是循序指向,且單向的產生器,當今天所有generator內部的元素都產生完畢,指針不會回頭,因此,不管是next或者for去取,只要指針已經指向尾巴(最後元素),要再重新打印一次是沒有辦法的next可以沒有辦法,但為什麼for也一樣呢?因為for也指到尾巴了,對generator來說當最後一個元素輸出,它裡面就空了,指針返回null的情況,是什麼都沒辦法印出,就算使用next接在for之後,一樣也無法
#推論完畢
不過注意一下,generator空間始終維持常數,所以你如果要把產生出來的東西放到某個list裡面處理,那list佔據的空間就是另外一回事,跟generator不相干,總結來說,list [..(10元素)..]
佔據10,而透過next()的generator產生出來的元素,為佔據1,...其實這樣的說法不太對,但想成這樣理解就可以發現透過generator耗費較少的空間去產生元素
佔據10,而透過next()的generator產生出來的元素,為佔據1,...其實這樣的說法不太對,但想成這樣理解就可以發現透過generator耗費較少的空間去產生元素
2.
yield
這是一個關鍵字,最常作用在函式中,當他在函式中,會將整個函式變為generator
例如:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
fib(6) # 1 1 2 3 5 8
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
>>f = fib(6)
>>f
<generator object fib at 0x104feaaa0>
參考這邊瞭解yield的方法:連結
普通的函式執行時候遇到return就離開,因為執行呼叫的方法是call stack的方式,但當函式中有yield時,就變成執行到yield 並回傳yield之後接的value,就離開,但函式會記得離開位置,當下次執行時候,從上次離開位置開始執行,直到讀到下一個yield
看例子比較明顯
def odd():
print('step 1')
yield 1
print('step 2')
yield 3
print('step 3')
yield 5
c=odd()
next(c) # step 1
1
再度下一次next
next(c) #step 2
3
所以fib()改用yield後,會有一樣的效果,使用for輸出
for n in fib(6):
print(n) #1 1 2 3 5 8
額外提一下,在function fib中我們會return done,但是用for是沒有辦法抓到 done
利用捕捉例外的方法:
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print(x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
1
1
2
3
5
8
Generator return value: done
其實yield使用上很方便,主要是代替複雜的計算...
不過個人覺得程式碼寫的簡單好看容易閱讀,yield使用不當可能會造成修改/閱讀上的困難
3.
Iterable指的是數據集合,更簡單的說就是,一開始已經決定好數據的內容
因此list,dict,set,tuple,string 就是iterable,可以用之前的isinstance來判斷是否是iterable,
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
不過另外一種iterator就不太一樣,就是跟上面相反,它沒有辦法一開始決定範圍,只能透過不斷next()去get出下一個元素的的就是iterator,所以上面提到的generator就是iterator,一樣,可以透過isinstance去判斷是否為iterator
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
3.
Iterable指的是數據集合,更簡單的說就是,一開始已經決定好數據的內容
因此list,dict,set,tuple,string 就是iterable,可以用之前的isinstance來判斷是否是iterable,
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
不過另外一種iterator就不太一樣,就是跟上面相反,它沒有辦法一開始決定範圍,只能透過不斷next()去get出下一個元素的的就是iterator,所以上面提到的generator就是iterator,一樣,可以透過isinstance去判斷是否為iterator
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
這邊簡單總結一下,所有的Generator都是iterator對象,但是但是,只要你是已經決定好的數據類型,例如list,dict,set,tuple,string ,就只是iterable,不是iterator,覺得麻煩就是這樣想就好,可以數的就是iterable,沒辦法馬上決定的就是iterator
上面可能有人會問為什麼(x for x in range(10)都會是true,因為在這邊是Generator,所以都適用
不過還是有方法可以讓iterable變成iterator,使用 iter()函式
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
True
>>> isinstance(iter('abc'), Iterator)
True
在Python中,iterator()的對象是可以用next()不斷的提取並返回下一個數據,直到沒有數據時就會拋出StopIteration,python把iterator()對象看成數據流,是一個有序序列,但不知道長度,只好不斷用next()去提取
所以相反的iterable(),就是已經知道所有的數據長度
==> iterable:可數的,確定數目的
==> iterator:送代的,無法確定數目,只能不斷net()或者get()直到null(StopIteration)發生
沒有留言:
張貼留言