5.1.24 Python之列表產生式、產生器、可迭代對象與迭代器

來源:互聯網
上載者:User

標籤:roo   錯誤   while迴圈   send   下一步   代碼   app   lte   裝飾器   

  • 文法糖的概念
  • 列表產生式
  • 產生器(Generator)
  • 可迭代對象(Iterable)
  • 迭代器(Iterator)
  • Iterable、Iterator與Generator之間的關係
一、文法糖的概念

“文法糖”,從字面上看應該是一種文法。“糖”,可以理解為簡單、簡潔。其實我們也已經意識到,沒有這些被稱為“文法糖”的文法,我們也能實現相應的功能,而 “文法糖”使我們可以更加簡潔、快速的實現這些功能。 只是Python解譯器會把這些特定格式的文法翻譯成原本那樣複雜的代碼邏輯而已,沒有什麼太高深的東西。

到目前為止,我們使用和介紹過的文法糖有:

  • if...else 三元運算式: 可以簡化分支判斷語句,如 x = y.lower() if isinstance(y, str) else y
  • with語句: 用於檔案操作時,可以幫我們自動關閉檔案對象,使代碼變得簡潔;
  • 裝飾器: 可以在不改變函數代碼及函數調用方式的前提下,為函數增加增強性功能;

這裡會再介紹兩個:

  • 列表產生式: 用於產生一個新的列表
  • 產生器: 用於“惰性”地產生一個無限序列
二、列表產生式

顧名思義,列表產生式就是一個用來產生列表的特定文法形式的運算式。

1. 文法格式:基礎文法格式
[exp for iter_var in iterable]

工作過程:

  • 迭代iterable中的每個元素;
  • 每次迭代都先把結果賦值給iter_var,然後通過exp得到一個新的計算值;
  • 最後把所有通過exp得到的計算值以一個新列表的形式返回。

相當於這樣的過程:

L = []for iter_var in iterable:    L.append(exp)
帶過濾功能文法格式
[exp for iter_var in iterable if_exp]

工作過程:

  • 迭代iterable中的每個元素,每次迭代都先判斷if_exp運算式結果為真,如果為真則進行下一步,如果為假則進行下一次迭代;
  • 把迭代結果賦值給iter_var,然後通過exp得到一個新的計算值;
  • 最後把所有通過exp得到的計算值以一個新列表的形式返回。

相當於這樣的過程:

L = []for iter_var in iterable:    if_exp:        L.append(exp)
迴圈嵌套文法格式
[exp for iter_var_A in iterable_A for iter_var_B in iterable_B]

工作過程:
每迭代iterable_A中的一個元素,就把ierable_B中的所有元素都迭代一遍。

相當於這樣的過程:

L = []for iter_var_A in iterable_A:    for iter_var_B in iterable_B:        L.append(exp)
2. 應用情境

其實列表產生式也是Python中的一種“文法糖”,也就是說列表產生式應該是Python提供的一種產生列表的簡潔形式,應用列表產生式可以快速產生一個新的list。它最主要的應用情境是:根據已存在的可迭代對象推匯出一個新的list。

3. 使用執行個體

我們可以對幾個產生列表的要求分別通過“不使用列表產生式”和“使用列表產生式”來實現,然後做個對比總結。

執行個體1:產生一個從3到10的數字列表
# 不使用列表產生式實現list1 = list(range(3, 11))# 使用列表產生式實現list2 = [x for x in range(3, 11)]
執行個體2:產生一個2n+1的數字列表,n為從3到11的數字
# 不使用列表產生式實現list3 = []for n in range(3, 11):    list3.append(2*n + 1)# 使用列表產生式實現list4 = [2*n + 1 for n in range(3, 11)]
執行個體3:過濾出一個指定的數字列表中值大於20的元素
L = [3, 7, 11, 14,19, 33, 26, 57, 99]# 不使用列表產生式實現list5 = []for x in L:    if x < 20:        list5.append(x)# 使用列表產生式實現list6 = [x for x in L if x > 20]
執行個體4:計算兩個集合的全排列,並將結果作為儲存至一個新的列表中
L1 = [‘香蕉‘, ‘蘋果‘, ‘橙子‘]L2 = [‘可樂‘, ‘牛奶‘]# 不使用列表產生式實現list7 = []for x in L1:    for y in L2:        list7.append((x, y))# 使用列表產生式實現list8 = [(x, y) for x in L1 for y in L2]
執行個體5:將一個字典轉換成由一組元組組成的列表,元組的格式為(key, value)
D = {‘Tom‘: 15, ‘Jerry‘: 18, ‘Peter‘: 13}# 不使用列表產生式實現list9 = []for k, v in D.items():    list9.append((k, v))# 使用列表產生式實現list10 = [(k, v) for k, v in D.items()]

可見,使用列表產生式確實要方便、簡潔很多,使用一行代碼就搞定了。

4. 列表產生式與map()、filter()等高階函數功能對比

我覺得,大家應該已經發現這裡說的列表產生式的功能與之前 這篇文章 中講到的map()和filter()高階函數的功能很像,比如下面兩個例子:

執行個體1:把一個列表中所有的字串轉換成小寫,非字串元素原樣保留
L = [‘TOM‘, ‘Peter‘, 10, ‘Jerry‘]# 用列表產生式實現list1 = [x.lower() if isinstance(x, str) else x for x in L]# 用map()函數實現list2 = list(map(lambda x: x.lower() if isinstance(x, str) else x,  L))
執行個體2:把一個列表中所有的字串轉換成小寫,非字串元素移除
L = [‘TOM‘, ‘Peter‘, 10, ‘Jerry‘]# 用列表產生式實現list3 = [x.lower() for x in L if isinstance(x, str)]# 用map()和filter()函數實現list4 = list(map(lambda x: x.lower(), filter(lambda x: isinstance(x, str), L)))

對於大部分需求來講,使用列表產生式和使用高階函數都能實現。但是map()和filter()等一些高階函數在Python3中的傳回值類型變成了Iteraotr(迭代器)對象(在Python2中的傳回值類型為list),這對於那些元素數量很大或無限的可迭代對象來說顯然是更合適的,因為可以避免不必要的記憶體空間浪費。關於迭代器的概念,下面會單獨進行說明。

三、產生器(Generator)

從名字上來看,產生器應該是用來產生資料的。

1. 產生器的作用

按照某種演算法不斷產生新的資料,直到滿足某一個指定的條件結束。

2. 產生器的構造方式

構造產生器的兩種方式:

  • 使用類似列表產生式的方式產生 (2*n + 1 for n in range(3, 11))
  • 使用包含yield的函數來產生

如果計算過程比較簡單,可以直接把列表產生式改成generator;但是,如果計算過程比較複雜,就只能通過包含yield的函數來構造generator。

說明: Python 3.3之前的版本中,不允許迭代函數法中包含return語句。

3. 產生器構造執行個體
# 使用類似列表產生式的方式構造產生器g1 = (2*n + 1 for n in range(3, 6))# 使用包含yield的函數構造產生器def my_range(start, end):    for n in range(start, end):        yield 2*n + 1g2 = my_range(3, 6)print(type(g1))print(type(g2))

輸出結果:

<class ‘generator‘><class ‘generator‘>
4. 產生器的執行過程與特性產生器的執行過程:

在執行過程中,遇到yield關鍵字就會中斷執行,下次調用則繼續從上次中斷的位置繼續執行。

產生器的特性:
  • 只有在調用時才會產生相應的資料
  • 只記錄當前的位置
  • 只能next,不能prev
5. 產生器的調用方式

要調用產生器產生新的元素,有兩種方式:

  • 調用內建的next()方法
  • 使用迴圈對產生器對象進行遍曆(推薦)
  • 調用產生器對象的send()方法
執行個體1:使用next()方法遍曆產生器
print(next(g1))print(next(g1))print(next(g1))print(next(g1))

輸出結果:

7911Traceback (most recent call last):  File "***/generator.py", line 26, in <module>    print(next(g1))StopIteration
print(next(g2))print(next(g2))print(next(g2))print(next(g2))

輸出結果:

7911Traceback (most recent call last):  File "***/generator.py", line 31, in <module>    print(next(g2))StopIteration

可見,使用next()方法遍曆產生器時,最後是以拋出一個StopIeration異常終止。

執行個體2:使用迴圈遍曆產生器
for x in g1:    print(x)for x in g2:    print(x)

兩個迴圈的輸出結果是一樣的:

7911

可見,使用迴圈遍曆產生器時比較簡潔,且最後不會拋出一個StopIeration異常。因此使用迴圈的方式遍曆產生器的方式才是被推薦的。

需要說明的是:如果產生器函數有傳回值,要擷取該傳回值的話,只能通過在一個while迴圈中不斷的next(),最後通過捕獲StopIteration異常

執行個體3:調用產生器對象的send()方法
def my_range(start, end):    for n in range(start, end):        ret = yield 2*n + 1        print(ret)g3 = my_range(3, 6)
print(g3.send(None))print(g3.send(‘hello01‘))print(g3.send(‘hello02‘))

輸出結果:

7hello019hello0211
print(next(g3))print(next(g3))print(next(g3))

輸出結果:

7None9None11

結論:

  • next()會調用yield,但不給它傳值
  • send()會調用yield,也會給它傳值(該值將成為當前yield運算式的結果值)

需要注意的是:第一次調用產生器的send()方法時,參數只能為None,否則會拋出異常。當然也可以在調用send()方法之前先調用一次next()方法,目的是讓產生器先進入yield運算式。

6. 產生器與列表產生式對比

既然通過列表產生式就可以直接建立一個新的list,那麼為什麼還要有產生器存在呢?

因為列表產生式是直接建立一個新的list,它會一次性地把所有資料都存放到記憶體中,這會存在以下幾個問題:

  • 記憶體容量有限,因此列表容量是有限的;
  • 當列表中的資料量很大時,會佔用大量的記憶體空間,如果我們僅僅需要訪問前面有限個元素時,就會造成記憶體資源的極大浪費;
  • 當資料量很大時,列表產生式的返回時間會很慢;

而產生器中的元素是按照指定的演算法推算出來的,只有調用時才產生相應的資料。這樣就不必一次性地把所有資料都產生,從而節省了大量的記憶體空間,這使得其產生的元素個數幾乎是沒有限制的,並且操作的返回時間也是非常快速的(僅僅是建立一個變數而已)。

我們可以做個實驗:對比一下產生一個1000萬個數位列表,分別看下用列表產生式和產生器時返回結果的時間和所佔記憶體空間的大小:

import timeimport systime_start = time.time()g1 = [x for x in range(10000000)]time_end = time.time()print(‘列表產生式返回結果花費的時間: %s‘ % (time_end - time_start))print(‘列表產生式返回結果佔用記憶體大小:%s‘ % sys.getsizeof(g1))def my_range(start, end):    for x in range(start, end):        yield xtime_start = time.time()g2 = my_range(0, 10000000)time_end = time.time()print(‘產生器返回結果花費的時間: %s‘ % (time_end - time_start))print(‘產生器返回結果佔用記憶體大小:%s‘ % sys.getsizeof(g2))

輸出結果:

列表產生式返回結果花費的時間: 0.8215489387512207列表產生式返回結果佔用記憶體大小:81528056產生器返回結果花費的時間: 0.0產生器返回結果佔用記憶體大小:88

可見,產生器返回結果的時間幾乎為0,結果所佔記憶體空間的大小相對於清單產生器來說也要小的多。

四、可迭代對象(Iterable)

我們經常在Python的文檔中看到“Iterable”這個此,它的意思是“可迭代對象”。那麼什麼是可迭代對象呢?
可直接用於for迴圈的對象統稱為可迭代對象(Iterable)。

目前我們已經知道的可迭代(可用於for迴圈)的資料類型有:

  • 集合資料類型:如list、tuple、dict、set、str等
  • 產生器(Generator)

可以使用isinstance()來判斷一個對象是否是Iterable對象:

from collections import Iterableprint(isinstance([], Iterable))
五、迭代器(Iterator)1. 迭代器的定義

可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator

很明顯上面講的產生器也是迭代器。當然,我們可以使用isinstance()來驗證一下:

from collections import Iteratorprint(isinstance((x for x in range(5)), Iterator))

輸出結果為:True

2. 對迭代器的理解

實際上,Python中的Iterator對象表示的是一個資料流,Iterator可以被next()函數調用被不斷返回下一個資料,直到沒有資料可以返回時拋出StopIteration異常錯誤。可以把這個資料流看做一個有序序列,但我們無法提前知道這個序列的長度。同時,Iterator的計算是惰性的,只有通過next()函數時才會計算並返回下一個資料。(此段內容來自 這裡)

產生器也是這樣的,因為產生器也是迭代器。

六、Iterable、Iterator與Generator之間的關係
  • 產生器對象既是可迭代對象,也是迭代器: 我們已經知道,產生器不但可以作用與for迴圈,還可以被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示無法繼續返回下一個值了。也就是說,產生器同時滿足可迭代對象和迭代器的定義;
  • 迭代器對象一定是可迭代對象,反之則不一定: 例如list、dict、str等集合資料類型是可迭代對象,但不是迭代器,但是它們可以通過iter()函數產生一個迭代器對象。

也就是說:迭代器、產生器和可迭代對象都可以用for迴圈去迭代,產生器和迭代器還可以被next()方函數調用並返回下一個值。

 

  

5.1.24 Python之列表產生式、產生器、可迭代對象與迭代器

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.