python3基礎篇(六)——迭代器與生成器,生成式

python3基礎篇(六)——迭代器與生成器,生成式

前言:
1 閱讀這篇文章我能學到什麼?
  這篇文章將爲你介紹python3的迭代器和生成器以及列表生成式的詳細用法。

——如果你覺得這是一篇不錯的博文,希望你能給一個小小的贊,感謝您的支持。

1 迭代器

  在python3中可以爲序列創建迭代器。迭代器是一個用於記錄在序列中當前遍歷位置的結構,持續訪問它將能依次訪問序列的所有元素。它從序列的第一個元素開始訪問,直到訪問完所有元素。迭代器只能前進不能後退,也即訪問過的元素不能再次訪問。常用的方法有iter()用於創建迭代器,next()函數返回當前元素並指向下一個元素。
代碼示例:

Tuple = (1, 2, 3)
Iterator1 = iter(Tuple)
print(next(Iterator1))
print(next(Iterator1))
print(next(Iterator1))
print("-------------------------")

List = [1, 2, 3]
Iterator2 = iter(List)               #爲列表創建一個迭代器
print(next(Iterator2))
print(next(Iterator2))
print(next(Iterator2))
print("-------------------------")

Set = {1, 2, 3}
Iterator3 = iter(Set)                #爲集合創建迭代器
print(next(Iterator3))
print(next(Iterator3))
print(next(Iterator3))
print("-------------------------")

Dictionary = {"1":1, "2":2, "3":3}
Iterator4 = iter(Dictionary)         #爲字典創建迭代器
print(next(Iterator4))
print(next(Iterator4))
print(next(Iterator4))
print("-------------------------")

string = "123"
Iterator5 = iter(string)             #爲字符串創建迭代器
print(next(Iterator5))
print(next(Iterator5))
print(next(Iterator5))

運行結果:

1
2
3
-------------------------
1
2
3
-------------------------
1
2
3
-------------------------
1
2
3
-------------------------
1
2
3

Process finished with exit code 0

  可以爲字符串、元組、列表、集合、字典創建迭代器,也可以把一個類作爲迭代器,或者說它們是“可以迭代的”,下面要講的生成器也是可以迭代的。迭代器也可和for循環配合使用。
代碼示例:

Tuple = (1, 2, 3)
Iterator1 = iter(Tuple)

for val in Iterator1:
    print(val)

運行結果:

1
2
3

2 生成式

  講生成器之前還需要補充一下python3中一個很強的特性即“生成式”。在創建列表時,允許將規則寫在列表中,python3將會按照此規則自動推導出列表的元素。這無疑是一種創建列表的簡潔寫法。

2.1 爲什麼需要“生成式”?

  假設讓你創建這樣一個列表,他的元素符合公式y=x2+xy=x^2+x,x爲自然數。如果告訴你只需要前五項,你可以很輕鬆的創建出這個列表

List = [0, 2, 6, 12, 20]

  如果我要求寫出前10項呢?當然也能寫出,不過這次要廢一點功夫去計算。

List = [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]

  如果是前100項呢?你可能會先創建一個空列表,然後寫一個循環函數去計算沒一個元素並將其裝入列表中。代碼這個樣子:

List = []                           #創建空列表
n = 100
for x in range(n):
    List.append(x * x + x)          #計算每一個元素將其裝載入列表
print(List)

  如果在python3中這樣寫將會非常”笨“的寫法。可以利用生成式這樣創建列表:

List = [x * x + x for x in range(100)]
print(List)

  我們將公式x2+xx^2+x直接寫在了列表的[]內,並且使用了forrange()函數限定了x的取值範圍。這樣python3就可以根據這些規則去自動推導出列表中的每一個元素。你可以將[]內的代碼分成兩部分來看,一部分是生成規則x * x + x,另一部分是循環結構的範圍限定for x in range(100)
  列表生成式主要作用就是使得創建列表變得更簡便。
  再思考一個問題,如果要求創建的列表元素包含前一千,甚至一萬項呢?當元素個數龐大的時候,會帶來另外一個問題,就是內存開銷過大。這時候我們可以犧牲一點時間效率去節省空間效率。讓列表只記錄計算規則(公式),當需要訪問元素時實時計算生成,不需要保存所有元素,這就是後面要講到的生成器的作用。

2.2 列表生成式語法規則

  活用生成式將會使得代碼更簡潔有效。

2.2.1 生成式給出規則和範圍

  上面我們已經看到了列表生成式中給出生成規則(公式)和參數範圍的語法結構了。再回顧一下:List = [x * x + x for x in range(100)]
  接下來介紹更復雜一點的用法以滿足更多的設計需求。

2.2.2 for語句之後加入if語句

  對於上面的列表我們添加一個規則,就是元素既要符合公式y=x2+xy=x^2+x,x爲自然數,而且要求x必須被能5整除。爲了滿足這個要求我們需要對元素進行 篩選。使用if語句進行篩選。
代碼示例:

List = [x * x + x for x in range(100) if x % 5 == 0]
print(List)

運行結果:

[0, 30, 110, 240, 420, 650, 930, 1260, 1640, 2070, 2550, 3080, 3660, 4290, 4970, 5700, 6480, 7310, 8190, 9120]

  篩選後100個元素只剩下20個了。只是在[]for語句之後添加了一句if x % == 0就對元素進行了篩選,只留下符合條件的元素。
  也可以在for語句之後使用多個if,設置多個篩選條件。
  下面的代碼篩選條件爲能被5和2整除的元素。
代碼示例:

List1 = [x * x + x for x in range(100) if x % 5 == 0 if x % 2 == 0]         #for語句後面使用兩個if,篩選同時滿足兩個if的條件
print(List1)
List2 = [x * x + x for x in range(100) if x % 5 == 0 and x % 2 == 0]         #for後面使用兩個if,篩選同時滿足兩個if的條件
print(List2)

運行結果:

[0, 110, 420, 930, 1640, 2550, 3660, 4970, 6480, 8190]
[0, 110, 420, 930, 1640, 2550, 3660, 4970, 6480, 8190]

  生成式中for語句之後加入if語句事先對元素的篩選,需要 注意 的是不能使用if-elseif-elifif-elif-else這些結構,只能使用if結構。

2.2.3 for語句之前加入if語句

  for語句前面使用if-else語句,它其實是生成規則(公式)的一部分,你可以簡單理解爲分段函數。要注意只能使用if-else結構。
  我們對上面的例子我們再增加一個條件,前100個元素中,前50個元素需要滿足公式y=x^2+x,後50個需要滿足公式y=-x^2+x。這是一個分段函數,根據x的值是否大於50去決定使用哪個公式,也即有一個選擇的動作,這就需要if-else去實現。
代碼示例:

List = [x * x + x if x < 50 else -(x * x) + x for x in range(100) if x % 5 == 0 and x % 2 == 0]         #for後面使用兩個if,篩選同時滿足兩個if的條件
print(List)

運行結果:

[0, 110, 420, 930, 1640, -2450, -3540, -4830, -6320, -8010]

  上面的代碼當滿足if x % 2 == 0

2.2.4 多個參數

  上面我們介紹的生成式都是一個參數即y=f(x)y=f(x)的形式,實際上是可以多個參數的,這裏我們以兩個參數舉例。兩個參數的生成式需要滿足公式z=f(x,y)z=f(x,y)。我們假設需要創建這樣一個序列:x參數是奇數,y參數是偶數。計算5以內的x與y的和。需要滿足公式z=x+yz=x+y且x∈[1, 3, 5],y∈[0, 2, 4]。
代碼示例:

a = [1, 3, 5]
b = [0, 2, 4]
List = [x + y for x, y in zip(a, b)]   #zip函數是python內置函數,將元組a和b打包爲元素是列表的元組
print(List)

  簡單解釋下zip()函數,它是python的內置函數,在這裏它的作用合併列表a和b生成列表[(1, 0), (3, 2), (5, 4)]。x和y的值就取自這個新元組中的元素,從而實現了兩個取值不同的參數求和。三個及以上的參數可以類推。

[1, 5, 9]

2.2.4 多個for

  多個for主要用於將多個參數的取值進行一一組合。比如兩個字符串"ABC""abc"它們的字符之間有哪些組合?

str1 = "ABC"
str2 = "abc"
List = [x + y for x in str1 for y in str2]  #兩個for
print(List)

  字符串相加就是把字符串拼接。兩個for分別遍歷了x和y的所有可能取值,則總的元素可能就是兩個for遍歷元素的組合。三個及以上個數的for可以類推。
  通過上面的學習你可能也驚歎這麼複雜的邏輯都可以在列表定義時就極其精簡的實現了,這確實是python強大的一個地方。

3 生成器

3.1 使用()創建生成器

  迭代器和可迭代的數據類型將所有元素都放在內存,當需要時進行訪問讀取,如果元素個數龐大將會非常消耗內存。這時候可以犧牲一點時間節省空間,我們使用生成器,它記錄的不是每個元素的值,而是生成元素的規則,當進行訪問時通過規則實時計算出來。由於生成器也是可迭代的,所以和迭代器一樣可以使用next()、和for循環進行迭代。
  下面代碼實現將列表元素用生成器產生,構建了一個生成器。
代碼示例:

List1 = [x * x + x for x in range(5)]       #使用生成式創建一個列表
List2 = (x * x + x for x in range(5))       #創建一個生成器,將[]改爲()
print(List1)
print(List2)                                #這是一個生成器
print("-------------------------")
Iterator1 = iter(List1)                     #爲列表創建一個迭代器
print(next(Iterator1))                      #迭代器可適用next()函數迭代
print(next(Iterator1))
print(next(Iterator1))
print("-------------------------")
print(next(List2))                          #生成器也是可迭代的,可以使用next(0函數迭代
print(next(List2))
print(next(List2))

print("-------------------------")
for val in Iterator1:                       #迭代器可適用for循環進行迭代,繼續往後迭代
    print(val)
print("-------------------------")
for val in List2:                           #生成器可適用for循環進行迭代
    print(val)

運行結果:

[0, 2, 6, 12, 20]
<generator object <genexpr> at 0x0000020FC495BE40>
-------------------------
0
2
6
-------------------------
0
2
6
-------------------------
12
20
-------------------------
12
20

Process finished with exit code 0

  生成器也是可迭代的,所以可使用next()函數進行迭代,也可使用for循環進行迭代。也有很多人將生成器看做一種特殊的迭代器。
生成器和迭代器共同點:
  都能記錄元素的當前訪問位置,都可被用next()for進行迭代,迭代只能向前不能退後。
生成器和迭代器不同點:
  在創建上迭代器使用iter()而生成器使用()yield創建。在迭代的過程中迭代器只是把事先存儲的值返回,而生成器需要按照規則去計算後返回。

3.2 使用yield創建生成器

  yield被用於函數中調用,使用了該關鍵字的函數就成了生成器。生成器函數與普通函數不同,它返回的是迭代器。yield有點類似return,它們能退出函數並返回一直值。不同的是return不會記錄函數的信息,而使用yield會記錄下函數當前運行的所有信息後退出函數並返回一個值,待下次再次調用這個函數時,繼續使用保存的信息並從上次退出的yield位置開始執行。通俗點說迭代器就是實現了一個“延遲”功能,它可以將函數的執行暫停。包括前面講的()創建的迭代器也是同樣的思想,不需要一次性將所有結果計算出來保存,而是當需要的時候進行計算並給出結果,最大目的就是爲了節省空間。
代碼示例:

def IteratorFunction():
    a = 0                       #局部變量
    a +=1
    yield a                     #執行到yield時保存當前運行的信息(比如變量a的值1),然後返回a的值並退出函數。
    a +=1
    yield a
    a +=1
    yield a

IF1 = IteratorFunction()        #創建一個迭代器
IF2 = IteratorFunction()        #創建第二個個迭代器

print(next(IF1))                #執行到yield時返回值1並記錄函數狀態後退出
print(next(IF1))                #再次調用從上一次yield位置繼續執行
print(next(IF2))                #IF1和IF是兩個獨立的迭代器
'''
#也可以調用用__next__()進行迭代
print(IF1.__next__())
print(IF1.__next__())
print(IF2.__next__())
'''

運行結果:

1
2
1

  這個專題還沒有講到函數,這裏稍微有點“超綱”涉及到函數了。
  以上在生成器函數裏放置了三個yield,可以理解爲三個“斷電”。經過多次調用和返回局部變量並沒有被釋放。生成器函數IteratorFunction並不能直接用於迭代,而是通過調用它後返回迭代器的特性,創建了TF1TF2兩個獨立的迭代器,也即能用於迭代的是TF1TF2。反覆調用TF1並不會改變TF2的運行信息。
  放置了三個yield,調用三次會分別停留在三個yield上,如果調用四次及以上會發生什麼?答案是會拋出StopIteration異常,因爲迭代器已經迭代到邊界了,也就是執行到return了,雖然我們省略了它。yield只是暫停return是真正的結束也意味着迭代到達邊界。一般我們會在生成器函數裏放上循環來使用。
  下面這個例子用生成器實現了斐波那契數列的迭代。

def Fibonacci_Iter():
    a, b = 1, 1             #斐波那契前兩項元素
    yield 1                 #第一個元素
    yield 1                 #第二個元素
    while(True):
        a, b = b, a + b
        yield b             #開始元素迭代

fb = Fibonacci_Iter()

print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))

運算結果:

1
1
2
3
5
8
13

  以上例子爲了簡單化和說明作用,我將while循環寫成了死循環,也即這個迭代器是沒有結束”邊界“的,現實計算機不能支持無限次調用的運算量,這是不好的示範,是爲了提醒大家不要只關注迭代器迭代推導的規則,還需要關注邊界。
重構這段代碼:

def Fibonacci_Iter():
    a, b = 1, 1                 #斐波那契前兩項元素
    IterNum = 0                 #迭代次數
    while(IterNum < 5):
        IterNum += 1            #迭代次數累加
        if IterNum <= 2:
            yield 1
        else:
            a, b = b, a + b
            yield b             #開始元素迭代
    return -1                   #執行到return就代表迭代到邊界了,-1將隨異常拋出

fb = Fibonacci_Iter()

print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))
print(next(fb))

運行結果:

1
1
2
3
5
Traceback (most recent call last):
  File "C:/Users/think/Desktop/Python_Test/Test.py", line 20, in <module>
    print(next(fb))
StopIteration: -1

  可以看到我們限定了迭代次數爲5次,當第六次迭代時由於執行到return迭代到達邊界停止,拋出了異常StopIteration: -1,其中-1既是我們return的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章