What is Iterator and Generator in Python?


What is Iterator and Generator in Python?

Iterator


Iterator 在各種語言中都有出現中文翻譯為迭代,迭代這名詞很讓人難以理解,不過我們其實很常使用就是 for in 語法,問個問題那麼 for in 是怎麼知道自己要跑幾次呢?

arr = [1, 2]
for i in arr:
    print(i) # 1 2 3

for in 實際上是呼叫 arr 裡面的 __iter__() 方法並取得一個 iterator 物件,然後每次再通過 iterator 物件中的 __next__() 方法不斷取得值,直到跑出 StopIteration 的例外。

arr = [1, 2]
iterator = arr.__iter__()
i = iterator.__next__() 
print(i) # 1
i = iterator.__next__() 
print(i) # 2
i = iterator.__next__() # StopIteration
print(i) 

output

1
2
Traceback (most recent call last):
  File "test.py", line 7, in <module>
    i = iterator.__next__()
StopIteration

這個行為其實就是我們很常說的遍歷,Iterator 被遍歷過一次之後就無法再次被遍歷

arr = [1, 2]
iterator = arr.__iter__()
for i in iterator:
    print(i)
for i in iterator:
    print(i)

output 只會印出一次 1 2 而不是兩次

1
2

這也很好理解 for i in iterator 也只是幫你 __next__(),這樣我們就可以很粗淺的理解 Iterator 的概念了。

Generatord


Generatord 其實也是 Iterator 的一種,這兩者有一個最大的區別,那就是 Iterator 已經在記憶體中產生資料才能讀取,而 Generatord 是在讀取的當下才產生資料,怎麼說呢?

from sys import getsizeof
arr = [i for i in range(100000)]
print(getsizeof(arr), " Bytes") # 824464 Bytes
gen = (i for i in range(100000))
print(getsizeof(gen), " Bytes") # 120 Bytes

同樣都是 100000 個值來說 Iterator 因為已經產生資料了所以佔用記憶體比較大,而 Generatord 一開始則只佔用了 120 Bytes,相對來說 Generatord 也必較適合用於非常龐大的資料。

補充一點 Generatord 也無法取得長度。

from sys import getsizeof
arr = [i for i in range(100000)]
print(arr.__len__()) # 100000
gen = (i for i in range(100000))
print(gen.__len__()) # AttributeError: 'generator' object has no attribute '__len__'

yield

那在使用 Generatord 前要先介紹 yield 關鍵字,yield 關鍵字 可以理解為可以 return 值但不會中斷 function 的關鍵字。

def getSomething(n):
    print("getSomething START")
    arr = []
    for i in range(0, n):
        print("some ", i)
        arr.append(i)
        yield arr[len(arr) - 1]
    print("getSomething END")
    return arr

something = getSomething(3)
print(type(something)) 

在上例中,我們呼叫 getSomething(3) 我們預期會得到這樣的 output

getSomething START
some 0 
some 1 
some 2
getSomething END
<<class 'list'>>

但其實 output 會是這樣,連 getSomething START 都不會拿到

<class 'generator'>

讓我們延續上例程式碼再往下加一行 i = something.__next__() 然後把 i 印出

def getSomething(n):
    print("getSomething START")
    arr = []
    for i in range(0, n):
        print("some ", i)
        arr.append(i)
        yield arr[len(arr) - 1]
    print("getSomething END")
    return arr

something = getSomething(3)
print(type(something)) 
i = something.__next__()
print(i)

會得到這樣的 output

<class 'generator'>
getSomething START
some  0
0

由此可知在我們執行 something.__next__() 之後 getSomething 這個方法才會第一次執行,那之後就跟 Iterator 差不多了 __next__() 到不能往下就會拋出 StopIteration

def getSomething(n):
    print("getSomething START")
    arr = []
    for i in range(0, n):
        print("some ", i)
        arr.append(i)
        yield arr[len(arr) - 1]
    print("getSomething END")
    return arr

something = getSomething(3)
print(type(something))
i = something.__next__()
print(i)
i = something.__next__()
print(i)
i = something.__next__()
print(i)
i = something.__next__() # StopIteration
print(i)

那最後要提醒 Generatord 其實也是 Iterator 的一種因此被遍歷過一次之後就無法一次遍歷啦,要重新產生一次 Generatord 才可以。

def getSomething(n):
    arr = []
    for i in range(0, n):
        arr.append(i)
        yield arr[len(arr) - 1]
    return arr

for i in something:
    print(i)
for i in something:
    print(i)

output 只會印出一次 1 2 3 而不是兩次

1
2
3

說到這裡 Iterator 和 Generatord 我們都粗略地瞭解過一遍了,謝謝各位收看。


WRITTEN BY
Aki

熱愛寫code的開發者,專注於 Android 手機 Native App 開發,對於 IOS 也有涉略。閒暇之餘也學習 JavaScript 等前端框架