1. 程式人生 > >Python小白學習之路(二十二)—【生成器】

Python小白學習之路(二十二)—【生成器】

表達式 視頻 控制 del 循環 有道 cor 數據量 分享圖片

一.什麽是生成器?

生成器可以理解成是一種數據類型,特殊地是生成器可以自動實現叠代器協議
其他的數據類型需要調用自己內置的__iter__方法
所以換種說法,生成器就是可叠代對象

!回憶:很重要的叠代器協議

對象必須提供一個 next 方法,執行該方法要麽返回叠代中的下一項,
要麽就引起一個Stoplteration異常,以終止叠代(只能往後走不能往前退)


二.生成器的分類(兩類)

python中生成器的表現形式
python中提供生成器的方式


一類是生成器函數;另一類是生成器表達式

第一類:關於生成器函數

  • 與常規函數定義相同。但是返回值時使用yield而不是return。
  • yield語句一次返回一個結果,可以進行多次返回(而return只能返回一次)
  • yield每次返回一個結果,在每個結果中間,掛起函數的狀態(其實就是記住我函數執行到哪一行了)
#舉例:
def test ():
    yield 1
g = test()      #並不會執行test()函數,需要通過 g.__next__()方法來觸發生成器函數執行
print(g) 
print(g.__next__())

#執行結果
<generator object test at 0x0051AA70>
1

在說生成器表達式之前,補充三元表達式和列表解析

三元表達式:(顧名思義,就是有三個元素唄)

以前我們是這麽寫程序的:

name = 
alex if name == alex: print(Ok) else: print(Not ok)


利用三元表達式我們是這麽寫程序的:

name = alex
res = Ok if name == alex else Not ok  #三元表達式
print(res)
#執行結果
Ok

技術分享圖片(每一圈為一個元)

列表解析:

列表解析式的語法格式為:

  • [i操作 for i in 列表 if 表達式1 and 表達式2]
  • (其實就是用中括號[]將三元表達式框起來)

舉例理解:
#我要通過程序下10個雞蛋
#以前我是這麽寫的

egg_list = []
for i in range(10): egg_list.append(雞蛋%s %i) print(egg_list) #執行結果 [雞蛋0, 雞蛋1, 雞蛋2, 雞蛋3, 雞蛋4, 雞蛋5, 雞蛋6, 雞蛋7, 雞蛋8, 雞蛋9]

#通過列表解析式我是這麽寫程序的

l0 = [ 雞蛋%s %i for i in range(10) ]
print(l0)
#執行結果
[雞蛋0, 雞蛋1, 雞蛋2, 雞蛋3, 雞蛋4, 雞蛋5, 雞蛋6, 雞蛋7, 雞蛋8, 雞蛋9]

l1 = [ 雞蛋%s %i for i in range(10) if i < 5 ]
print(l1)
#執行結果
[雞蛋0, 雞蛋1, 雞蛋2, 雞蛋3, 雞蛋4]

l2 = [ 雞蛋%s %i for i in range(10) if i < 3 or i > 7]
print(l2)
#執行結果
[雞蛋0, 雞蛋1, 雞蛋2, 雞蛋8, 雞蛋9]


總結:列表解析式優缺點
優點:取值方便(如果列表的長度較小時使用列表解析會很方便,)
缺點:如果列表的長度很大的時候,使用列表解析會占用很多的內存資源,此時可以使用生成器表達式來節省內存資源

第二類:關於生成器表達式

生成器表達式:(就是將列表解析式的中括號變成圓括號)

#舉例:
l0 = (雞蛋%s %i for i in range(10))
print(l0)
print(l0.__next__())
print(l0.__next__())
print(l0.__next__())

#執行結果
<generator object <genexpr> at 0x0045AA70>
雞蛋0
雞蛋1
雞蛋2

小結:
1.將列表解析式的 [] 換成 () 得到的就是生成器表達式
2.列表解析式與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3.python使用叠代器協議讓 for 循環變得更加通用。大部分內置函數也是使用叠代器協議來訪問對象的

舉例:

sum 函數

sum(x ** 2 for x in range(4))  #sum 直接按照叠代器協議訪問對象(類比for循環)

sum([x ** 2 for x in range(4)])  #所以並不需要將對象 x ** 2 for x in rang(4) 加上一個中括號變成列表解析式,將所有的值取出來構成一個列表再進行求和運算

三、通過兩段程序代碼來感受一下生成器的優勢

#今天所舉得列子不是下蛋就是吃包子(視頻課上老師就是這麽講的)
#我也深深的懷疑
#為什麽老師這麽鐘愛吃包子和下雞蛋

#下蛋程序一:
def xiadan():
    res = []
    for i in range(10000):
        res.append(雞蛋%s %i)
    return res
print(xiadan())

#缺點一:占用空間較大
#缺點二:效率低    

#下蛋程序二:
def xiadan():
for i in range(10000):
    yield 雞蛋%s %i
lmj = xiadan()
print(lmj.__next__())

#第一段程序是一旦執行 xiadan()這個函數,先下了10000個雞蛋來占用內存空間,在去執行其他操作
#第二段程序是通過生成器函數yield來返回我所需要的雞蛋,我邊用(通過__next__() 觸發生成器函數)雞蛋,邊下雞蛋

以生成器函數為例,對生成器進行總結

  • 語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義

差別在於生成器使用yield語句進行返回一個值,而常規函數使用return語句返回一個值

  • 自動實現叠代器協議:對於生成器,python會自動實現叠代器協議,以便應用到叠代器背景中

由於生成器自動實現了叠代器協議,所以我們可以直接調用它的next方法,並且在沒有值可以返回的時候生成器自動生成Stoplteration異常

  • 狀態掛起:生成器使用yield語句返回一個值。yield 語句掛起該生成器函數的狀態,保留足夠的信息,以便之後從它離開的地方繼續執行


優點一:
生成器的好處就是延遲計算,一次返回一個結果。也就是說它不會一次生成所有的結果,這對於大數據量
處理非常有用(前面下雞蛋的例子)

優點二:
生成器還能提高代碼可讀性

1.使用生成器以後,代碼行數更少(在保證代碼可讀性的前提下,代碼行數越少越好)
2.不適用生成器,對於每次結果,我們首先看到的是result.append(index),其次才是index
也就是說,我們每次看到的,是一個列表的append操作,只是append的是我們想要的結果。
使用生成器的時候,直接yield index,少了列表的append操作的幹擾,我們一眼能夠看出,代碼是要進行什麽操作。

四、觸發生成器執行的三種方式

    • 方式一:__next__()
    • 方式二:next()
    • 方式三:send()
#舉例:

def xiadan():
    for i in range(10000):
    yield 雞蛋%s %i

g = xiadan()
print(g.__next__())
print(next(g))
print(g.send(None))

#執行結果
雞蛋0
雞蛋1
雞蛋2

關於 send() 總結來源於一下文章並且結合自己的理解
看到一篇關於 yield 總結特別好的文章
鏈接:https://www.cnblogs.com/renpingsheng/p/8635777.html

send()必須傳一個參數,可為 None 或者 其他值

作用:


1. yield相當於return ,控制的是函數的返回值
2. x = yield的另外一個特性,接受send傳過來的值,賦值給 x

舉例理解:

def test():
    print(開始執行函數)
    first = yield
    print(第一次, first)
    second = yield
    print(第二次, second)
    yield
g = test()
print(next(g))
print(g.send(1))
print(g.send(2))

#程序執行過程分析
# 1.程序開始執行以後,因為test函數中有yield關鍵字,所以test函數並不會真的執行,而是先得到一個生成器g.
# 2.直到調用next方法,test函數正式開始執行,先執行test函數中的print方法,打印開始執行函數。然後執行first = yield
# 3.程序遇到yield關鍵字,程序暫停,此時next(g)語句執行完成,打印next(g)執行結果,即yield傳回的結果,為None
# 4.程序執行g.send(1),程序會從yield關鍵字那一行繼續向下運行,send會把1這個值傳遞給yield
# 5.yield接收到send方法傳遞過來的值,然後由yield賦值給first變量
# 6.由於send方法中包含next()方法,所以程序會繼續向下運行執行print方法,然後再次遇到yield關鍵字,程序暫停,此時g.send(1)語句執行完成,打印g.send(1)執行結果,即yield傳回的結果,為None
# 7.程序執行g.send(2),程序會從yield關鍵字那一行繼續向下運行,send會把2這個值傳遞給yield
# 8.yield接收到send方法傳遞過來的值,然後由yield賦值給second變量
# 9.由於send方法中包含next()方法,所以程序會繼續向下運行執行print方法,然後再次遇到yield關鍵字,程序暫停,此時g.send(2)語句執行完成,打印g.send(2)執行結果,即yield傳回的結果,為None

寫在後面:

珍愛眼睛 遠離電子產品

從上了研究生階段 眼睛就開始有虹膜炎

隔段時間就來打擾我

不能看電腦不能看手機不能看強光

還拼命流眼淚

我也真是佩服自己

正好這個階段我就在看書學python 也抽時間看了 許三觀賣血記

文學素養還是要培養的

我要做祖國新時代的四有新人 有文化 有道德 有。。。還有什麽來著

哈哈

愛吃火鍋的人運氣不會太差

愛吃火鍋的人怎麽可能輕易放棄



Python小白學習之路(二十二)—【生成器】