1. 程式人生 > >決戰Python之巔(十三)

決戰Python之巔(十三)

前言

拖了這麼久才來補生成器和迭代器- -

知識回顧

列表生成式

之前在講列表的時候並沒有經過這個東西,現在我來介紹一下。
列表生成式可以用一句話就能生成一個列表,如a = [x for x in range(10)],這樣就能直接生成一個0~9的列表。相對於利用for迴圈、while迴圈來說,更簡單快捷。當然你也可以這樣做b = [x**2 for x in range(10)],注意前面公式最多支援三元運算。

生成器

現在,我有一個需求,即生成一個有100000000(1億)個元素的列表,如果像上面一樣:d = [x for x in range(100000000)],一般的電腦請不要輕易嘗試,這種放縱的滋味會讓你的電腦宕機。壕隨意- -。但是即使配置好也發現需要等待一段時間才能給出結果,即使你不用列表生成式,用迴圈做也一樣。這是因為列表生成式總會把元素全都建立好才會返回,如果資料大一點,那將會佔用很多記憶體,但我們實際使用的可能只有裡面的一小部分,這樣便做了很多無用功。
那麼,怎麼應對這種情況呢?有這麼一個東西,可以將列表元素按照你規定的演算法算出,但只有你需要的時候才會生成,你用多少調多少,豈不快哉?
這就是生成器,官方解釋如下:

如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?
這樣就不必建立完整的list,從而節省大量的空間。
在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator。-------沃.鎡基碩得

那怎麼建立呢?

生成器建立方式一

與列表生成式類似,a = (x for x in range(10)),區別僅僅在於中括號變成了小括號。
在這裡插入圖片描述
這樣,你就有了一個生成器。那怎麼用這個生成器呢?這裡我們要用到一個next()語法,字面意思就是下一個,我們來試下效果:
在這裡插入圖片描述
每呼叫一次next(a),便返回一個元素。注意這裡只能不斷向前進,不能後退,即當next(a)返回3的時候,你不可能在next(a)返回2出來,前面的過了就過了,不能再回去了。
當你next返回最後一個元素後,再次next,會有什麼效果?
在這裡插入圖片描述


這裡會丟擲一個StopIteration的異常,告訴你生成器已經到頭了,再接下去就沒有了。(這個當我們後面學了捕捉異常之後另有用處)。
當然,一次次呼叫next(a)不太現實,這裡我們可以結合迴圈來做:
在這裡插入圖片描述
你會發現,當使用for迴圈的時候,就算到了盡頭,也不會報錯,即不需要關心StopIteration的錯誤。

生成器建立方式二

有其一必有其二。如果推算的演算法比較複雜,用類似列表生成式的for迴圈無法實現的時候,還可以用函式來實現。
我們用斐波拉契函式做示範:

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()距離生成器只有一步之遙:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
   # return 'done'

這就是定義generator的另一種方法。如果一個函式定義中包含yield關鍵字,那麼這個函式就不再是一個普通函式,而是一個generator:
在這裡插入圖片描述
生成器與函式的執行流程不太一樣:
在這裡插入圖片描述
在每次呼叫next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。

迭代器

我們已經知道,可以直接作用於for迴圈的資料型別有以下幾種:
一類是集合資料型別,如list、tuple、dict、set、str等;
一類是generator,包括生成器和帶yield的generator function。
這些可以直接作用於for迴圈的物件統稱為可迭代物件:Iterable。可以使用==isinstance()==判斷一個物件是否是Iterable物件:
在這裡插入圖片描述
而生成器不但可以作用於for迴圈,還可以被next()函式不斷呼叫並返回下一個值,直到最後丟擲StopIteration錯誤表示無法繼續返回下一個值了。
可以被next()函式呼叫並不斷返回下一個值的物件稱為迭代器:Iterator。
可以使用isinstance()判斷一個物件是否是Iterator物件:
在這裡插入圖片描述
生成器都是Iterator物件,但list、dict、str雖然是Iterable,卻不是Iterator。
把list、dict、str等Iterable變成Iterator可以使用iter()函式:
在這裡插入圖片描述
你可能會問,為什麼list、dict、str等資料型別不是Iterator?
這是因為Python的Iterator物件表示的是一個數據流,Iterator物件可以被next()函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration錯誤。可以把這個資料流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下一個資料,所以Iterator的計算是惰性的,只有在需要返回下一個資料時它才會計算。
Iterator甚至可以表示一個無限大的資料流,例如全體自然數。而使用list是永遠不可能儲存全體自然數的。