iT邦幫忙

2

【python入門教室】(10) 認識python的生成器與迭代器

嗨嗨,大家好,今天要來介紹python中較為高級的特性- 生成器與迭代器
其實做為初學者,
你寫程式可能也不太會寫到生成器,
只是為了完整的介紹python語法就順便講了,
若暫時覺得看不太懂先跳過此篇也沒關係
(其實也是因為生成器迭代器的概念不易理解,
小馬也沒什麼把握講的很簡單,只能盡力/images/emoticon/emoticon25.gif)

列表生成式 v.s. 生成器

首先先介紹什麼是生成器,
【python入門教室】(7) python 的列表生成式語法介紹中學過列表生成式,
例如要計算1的平方到5的平方存到列表中可以這樣寫:

L = [x*x for x in range(1,6)]
print(L)

結果: [1, 4, 9, 16, 25]
要創建一個生成器非常簡單,
只要把列表外面的[]改成()即可,例如:

g = (x*x for x in range(1,6))
print(g)

結果印出了<generator object <genexpr> at 0x000002917EA8F138>
那要怎麼樣可以印出生成器裡面的東西呢?
我們可以使用next()方法得到生成器的下一個元素:

g = (x*x for x in range(1,6))
print(next(g)) #印出 1
print(next(g)) #印出 4
print(next(g)) #印出 9
print(next(g)) #印出 16
print(next(g)) #印出 25
print(next(g)) #沒有元素了,出現StopIteration

但是我們比較少用next()方法取得下一個元素,
比較常見的方法為直接用for迴圈迭代:

例如:

g = (x*x for x in range(1,6))
for n in g:
    print(n)

結果:
1
4
9
16
25

我們也可以直接把生成器轉成列表:

g = (x*x for x in range(1,6))
print(list(g))

結果: [1, 4, 9, 16, 25]

呃…要生成器幹嘛?能吃嗎?

看到這邊大家大概會有個疑問,
普通的列表與生成器都可以用for迴圈來迭代,
平時用起來感覺功能也差不多,
那到底我們要生成器幹嘛?

答案是省空間
試想假設我們要創建一個含有一百萬個元素的列表,
但是可能實際會用到的元素只有前面幾個,
這時後面的空間都浪費掉了。
生成器保存的只是一種算法
不會像列表生成式一樣,
要在一開始就把所有東西算好存起來,
而是邊循環邊計算,
節省大量的空間。

補充: 難以理解的函數版本生成器

上述將列表生成式的中括號直接改成小括號是簡單寫生成器的方法,
但另一種較難理解的是在函數裡面寫yield關鍵字

譬如說我想要有一個無限生成器,
可以取得所有的偶數
初始值是2,之後每次呼叫next()方法時就印出下一個偶數,
可以這樣寫:

def allEven():
    n = 2 # 初始值n=2
    while True:
        yield n
        n = n+2

只要在函數裡面寫到yield這個字,那麼這個函數就不是一個普通的函數,
這個函數會在每次呼叫next()方法時執行到yield的地方停下來,
下次再執行函數時會再回到上次中斷的地方繼續執行

def allEven():
    n = 2 # 初始值n=2
    while True:
        yield n
        n = n+2
        
evens = allEven()
print(next(evens))
print(next(evens))
print(next(evens))

會依序印出
2
4
6

這便是一個生成器比列表更廣的地方,
一個列表裡面永遠無法儲存無限個元素,
但是可以用生成器表示無限的數據(如: 全體自然數),
生成器並不是真正存下無限個元素,而是需要用到下一個數據時才去計算

生成器 vs. 迭代器

講完了生成器,那什麼是迭代器呢?
生成器是迭代器的一種,
只是生成器特指以列表生成式語法加小括號產生的迭代器(或寫函數+yield語法),
而凡是能透過next()方法取得下一個元素的都是迭代器
(例如內建函數map, filter會返回迭代器,下一篇會提)。

(注意平時常見的list, str, range雖然可迭代,但不算迭代器)
迭代器的特性便是需要用到資料時才會去計算下一個元素,
以節省大量的空間。

重點:

  • 可以用for迴圈遍歷的容器稱為可迭代,但不一定是迭代器
  • 可以透過next()方法取得下一個元素的都是迭代器,需要用到資料時才會去計算下一個元素,節省大量的空間

注意因為迭代器是用到資料時才會去計算下一個元素,
迭代器本身沒有把元素事先存起來,
因此不能像列表一樣隨機存取(用index取值),
也不能進行切片運算。
例如下面程式print(g[1])是不合法的:

g = (x*x for x in range(1,6))
print(g[1])

會印出'generator' object is not subscriptable 的錯誤訊息。

參考資料

  1. 廖雪峰的官方網站- python教程- 這邊也有網友教python的生成器與迭代器的觀念,如果覺得小馬本篇講的很難懂,也可交叉參考著閱讀,找到適合自己理解的版本
  2. TutorialsTeacher: Python - Generator- 若你英文還不錯,這邊亦有英文教python生成器的網站可參考

尚未有邦友留言

立即登入留言