2017年7月5日 星期三

[Python]Generator-and-iterator-or-iterable

1.
 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耗費較少的空間去產生元素


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

這邊簡單總結一下,所有的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

在Python中,iterator()的對象是可以用next()不斷的提取並返回下一個數據,直到沒有數據時就會拋出StopIteration,python把iterator()對象看成數據流,是一個有序序列,但不知道長度,只好不斷用next()去提取

所以相反的iterable(),就是已經知道所有的數據長度

==> iterable:可數的,確定數目的
==> iterator:送代的,無法確定數目,只能不斷net()或者get()直到null(StopIteration)發生




沒有留言:

張貼留言

[Python] Partial function

在Python中有許多偏函數( Partial function)可以使用,常見的就是string的轉換 ex : int('50') #50 這個例子就是把string type的50轉換成int的50,而且是10進制 那好,問題來了,能不能變成8...