Python 入門 07 —— 推導式、生成器、解包

一、推導式
推導式其實是循環語句一個簡寫,以用於快捷地生成列表、元組、字典和集合。例如:

lst1 = [x for x in range(5)]
lst2 = tuple([x for x in range(5)])
lst3 = {x:chr(65+x) for x in range(5)}
lst4 = {x for x in range(5)}
print(lst1,lst2,lst3,lst4)
# [0, 1, 2, 3, 4] (0, 1, 2, 3, 4) {0:‘A’, 1:‘B’, 2:‘C’, 3:‘D’, 4:‘E’} {0, 1, 2, 3, 4}

推導式第一部分是一個運算式,可以有函數,運算的結果就作爲序列的一個元素。不斷的循環,不斷地產生元素,所以元素構成一個序列。

1、推導式的後面部分是一個簡寫的循環語句,這個循環語句還可以嵌套。例如:

lst1 = [str(x)+’-’+str(y) for x in range(5) for y in range(2)]
lst2 = [str(x)+’-’+str(y) for x in range(5) for y in range(x)]
print(lst1,lst2,sep=’\n’)
# [‘0-0’, ‘0-1’, ‘1-0’, ‘1-1’, ‘2-0’, ‘2-1’, ‘3-0’, ‘3-1’, ‘4-0’, ‘4-1’]
# [‘1-0’, ‘2-0’, ‘2-1’, ‘3-0’, ‘3-1’, ‘3-2’, ‘4-0’, ‘4-1’, ‘4-2’, ‘4-3’]

2、在推導式的最後面還可以加一個 if 語句來過濾元素。例如:

lst1 = [x**2 for x in range(10) if x>=5]
lst2 = [str(x)+’-’+str(y) for x in range(5) for y in range(x) if x+y>3]
print(lst1,lst2,sep=’\n’)
# [25, 36, 49, 64, 81]
# [‘3-1’, ‘3-2’, ‘4-0’, ‘4-1’, ‘4-2’, ‘4-3’]

二、生成器
生成器從表面上看與推導列一樣,也是相當於循環語句的簡寫,只有在最外面限定只能用小括號,但生成器與推導式有兩點本質的不同:
一是生成器最後生成的不是序列,而是一個生成器迭代對象,當然這個生成器迭代對象可以輕易地轉換成列表、元組、字典和集合;
二是生成器在編譯時僅生成一個生成器對象,只在被調用時才創建生成器迭代對象。生成器對象相當於一臺機器,生成器迭代對象相當於產品。

舉例:
gen = (x for x in range(5)) # 最外面限定只能用小括號
print(gen) # 產生的是生成器迭代對象 <generator object at 0x00000000024CEDD0>
print(list(gen)) # 調用時會創建生成器迭代對象,生成器迭代對象很容易轉換成序列:[0,1,2,3,4]

推導列能用的循環嵌套和最後加一個 if 語句的方法,在生成器中一樣能用。
gen2 = (str(x)+’-’+str(y) for x in range(10) for y in range(x) if x+y>13)
print(list(gen2)) # [‘8-6’, ‘8-7’, ‘9-5’, ‘9-6’, ‘9-7’, ‘9-8’]

以上說的僅是用於生成序列的生成器,因爲它的外面用的是小括號,所以可稱之爲:元組型生成器。另外,當一個函數中有“yield”關鍵詞時,這個函數就變成了一個生成器型函數(又稱:函數型生成器)。

很多人將元組型生成器稱作“元組生成器”,我認爲這不是很妥當,因爲這很容易被誤認爲這個生成器是專門用於生成元組的,而其實不是,它最終生成的是一個生成器迭代對象,這個生成器迭代對象可以被轉換成元組,也可以被轉換成其它序列。

有 yield 的生成器型函數與無yield 的普通函數相比,區別還是很大的。

1、生成器型函數只有作爲next()參數被調用時纔會被執行,其它方式無法使用這種函數。

def fun01():  # 無 yield 的普通函數
    print('++');  print('++++')

def fun02():  # 有 yield 的成器型函數
    print('++'); yield 6; print('++++')
f02 = fun02()
f02    # 生成器型函數不運行,不顯示任何內容
print(f02)    # f02 本身只是一個對象:<generator object fun02 at 0x0000000001D9EDD0>

f03 = fun02()
next(f03)    # 生成器型函數只有被next()調用時纔會運行,顯示:++

2、生成器型函數在運行時,遇yield關鍵詞則返回其後的值,然後停止,例如:

f04 = fun02()
print(next(f04))    # 顯示如下:
++    # 這是遇yield之前的print('++')語句顯示的
6    # 返回yield關鍵詞後面的值,函數運行完yield這一句就停止

3、再次用next()調用生成器型函數時,函數不從第一句開始運行,而是從上一次停止的地方開始運行,即接着上次的繼續運行。運行到下一個yield關鍵詞那裏又停止。那問題是如果沒有了“下一個yield關鍵詞”呢?沒有了就拋出異常,一切停止。

f05 = fun02()
print(next(f05))    #  顯示如下:
    ++
    6
print(next(f05))    #  顯示如下:
    ++++    # 上一次運行到yield這一句,這一次從這一句之後開始運行
    StopIteration    # 函數結束前但沒有遇見新的yield關鍵詞,則拋出異常

def fun03():
    print('++'); yield 6
    print('++++'); yield 7
    print('++++++'); yield 8
    print('++++++++')

f06 = fun03()
print(next(f06))    # 顯示如下:
    ++
    6
print(next(f06))    # 顯示如下:
    ++++
    7
print(next(f06))    # 顯示如下:
    ++++++
    8
print(next(f06))    # 顯示如下:
    ++++++++
    StopIteration    # 函數結束前但沒有遇見新的yield關鍵詞,則拋出異常

4、由上可見,生成器型函數用着用着就會拋出異常,解決這個問題方法也很簡單,就是將yield關鍵詞放到一個無限循環中,這樣就可以無限調用了。另外,這種函數有遇見 yield 自動停止的功能,所以在無限循環中,也不用擔心會進入死循環。

def fun04(x=5):
    while True: yield x ; x+=1

f07 = fun04(8)
print(next(f07),next(f07),next(f07),next(f07),next(f07),next(f07))    # 8 9 10 11 12 13

總之,生成器型函數猶如一支牙膏,不擠不出,擠一下出一點,擠完了就拋出異常。

三、解包
解包就是用一個簡潔的方式給多個變量賦值。等號左邊多個待賦值的變量用逗號“,”分隔,右邊一般是列表、元組等序列。例如:
x1,y1,z1 = [16,17,18]
x2,y2,z2 = (26,27,28)

x3,y3,z3 = dict({1:36,2:37,3:38}).items()
x4,y4,z4 = {46,47,48}

print(x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4) # 16 17 18 26 27 28 (1,36) (2,37) (3,38) 48 46 47

在for循環語句中也可以使用解包。例如:

for i,v,n in [(1,2,3),(11,12,13),(21,22,23)]:
    print(i,v,n,end=' ; ')  # 1 2 3 ; 11 12 13 ; 21 22 23 ;

———————————————— 本篇完 ————————————————

看完之後,麻煩您順手點擊下方 “點贊” 兩個字給我點個贊吧 ^-^ , 謝謝您了。

如果您還能像我小學一年級班主任好老師那樣,給我隨心寫上幾句表揚或批評的話語,那真是感激不盡!

在我人生的道路上,有了您的鼓勵和指導,我一定成長快快。

發佈了30 篇原創文章 · 獲贊 6 · 訪問量 3602
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章