一、生成器的概念
二、生成器函數的定義
1、yield和return關鍵字的區別和相同點
(1)yield和return關鍵字的的不同點:
(2)yield和return關鍵字的的相同點:
2、生成器函數初識
(1)什麼是生成器函數
(2)生成器函數的好處
三、生成器函數初級進階
1、從生成器中取值的兩種方法
(1)、方法一:next方法
(2)、方法二:send方法
2、預激協程的裝飾器
3、Python3新加的yield from
4、回顧
四、生成器函數高級進階
1、生成器的表達式和各種推導式
(1)、列表推導式
(2)、生成器的表達式
(3)、字典推導式
(4)、集合推導式(自帶結果去重功能)
2、總結
我們知道的迭代器有兩種:一種是調用方法直接返回的,比如for循環就是Python自帶的迭代器,一種是可迭代對象通過執行iter方法得到的,迭代器有的好處是可以節省內存。避免將大量數據一次性取出而導致的錯誤和內存不足問題。
如果在某些情況下,我們也需要節省內存,就只能自己寫。我們自己寫的這個能實現迭代器功能的東西就叫生成器。
簡而言之:生成器就是我們自己寫的迭代器
yield是用於生成器。什麼是生成器,你可以通俗的認為,在一個函數中,使用了yield來代替return的位置的函數,就是生成器。
(1)yield和return關鍵字的的不同點:
它不同於函數的使用方法是:函數使用return來進行返回值,每調用一次,返回一個新加工好的數據返回給你;yield不同,它會在調用生成器的時候,把數據生成object,然後當你需要用的時候,要用next()方法來取,同時不可逆。你可以通俗的叫它"輪轉容器",可用現實的一種實物來理解:水車,先yield來裝入數據、產出generator object(你執行了含有yield關鍵字的函數,之後調用該函數不會的到返回值而是得到一個可迭代的對象"generator object")使用next()來釋放;
好比水車轉動後,車輪上的水槽裝入水,隨著輪子轉動,被轉到下面的水槽就能將水送入水道中流入田裡。水車這個比方太恰當不過了,就是每次有個數據要取出來,先按照順序將數據放進水車的水槽中,當後面在調用next函數的時候相當於使用水槽的水,並且是按照水車中原來進去的順序進行取水的(先進先出)‘
(2)yield和return關鍵字的的相同點:
相同點:都是返回函數執行的結果
不同點:return 在返回結果後結束函數的運行,而yield 則是讓函數變成一個生成器(或者叫做可迭代對象),生成器每次產生一個值(yield語句),函數被凍結,被喚醒後再產生一個值用一個栗子總結:
def f1(): return 'aaa' return 'bbb' def f2(): yield 'aaa' yield 'bbb' print(f1()) print(f2()) for i in f2(): print(i) 輸出結果: aaa <generator object f2 at 0x000001731AFFFC10> aaa bbb
(1)什麼是生成器函數
一個包含yield關鍵字的函數就是一個生成器函數。yield可以為我們從函數中返回值,但是yield又不同於return,return的執行意味著程序的結束,調用生成器函數不會得到返回的具體的值,而是得到一個可迭代的對象。每一次獲取這個可迭代對象的值,就能推動函數的執行,獲取新的返回值。直到函數執行結束。
簡而言之就是函數內部含有yield關鍵字的就是生成器函數
(2)生成器函數的好處
生成器有什麼好處就是不會一下子在內存中生成太多數據,而是你找它要它才給你值,你不向它要它也不會返回值給你。
舉個栗子:
假如我預定了2000000件秋裝服,我和工廠一說,工廠應該是先答應下來,然後再去生產,我可以一件一件的要,也可以一批一批的找工廠拿。而不能是一說要生產2000000件衣服,工廠一次性生產2000000件衣服在一起給我,等回來做好了,都冬天了。。。def produce(): """生產衣服""" for i in range(2000000): yield "生產了第%s件衣服"%i product_g = produce() print(product_g.__next__()) #要一件衣服 print(product_g.__next__()) #再要一件衣服 print(product_g.__next__()) #再要一件衣服 num = 0 for i in product_g: #要一批衣服,比如5件 print(i) num +=1 if num == 5: break #到這裡我們找工廠拿了8件衣服,我一共讓我的生產函數(也就是produce生成器函數)生產2000000件衣服。 #剩下的還有很多衣服,我們可以一直拿,也可以放著等想拿的時候再拿
def generator(): print(123) yield 1 print(456) yield 2 g1 = generator() #生成器g1 g2 = generator() #生成器g2 print('*',g1.__next__()) #從生成器g1中取第一個值 print('**',g1.__next__()) #從生成器g1中取第二個值 print('***',g2.__next__()) #從生成器g2中取第一個值 輸出結果: 123 * 1 456 ** 2 123 *** 1
- send 獲取下一個值的效果和next基本一致
只是在獲取下一個值的時候給上一個yield的位置傳遞了一個數據
- 使用send的注意事項:
- 第一次使用生成器時候是用next獲取下一個值
- 最後一個yield不能接收外部的值
總結:
send:不能用在第一個,取下一個值的時候給上一個位置傳一個新的值
def generator(): print(123) content = yield 1 print('=======',content) print(456) yield 2 #send 獲取下一個值的效果和next基本一致 #只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據 #使用send的注意事項 # 第一次使用生成器的時候 是用next獲取下一個值 # 最後一個yield不能接受外部的值 g = generator() print('*',g.__next__()) ret = g.send('hello') #send的效果與next一樣 print('***',ret) 輸出結果: 123 * 1 ======= hello 456 *** 2
什麼是預激協程的裝飾器?
簡單來說就是個加了裝飾器的生成器(裝飾器介紹)
預激協程的裝飾器的實例:
#實際上就是省略了avg_g = average() avg_g.__next__()這兩步 def init(func): #func = average ''' 裝飾器函數 :param func: :return: ''' def inner(*args,**kwargs): g = func(*args,**kwargs) #g = average() g.__next__() return g return inner @init #average = init(average) = inner def average(): sum = 0 count = 0 avg = 0 while 1: #num = yield num = yield avg sum += num count += 1 avg = sum/count avg_g = average() #===>inner ret = avg_g.send(10) print(ret) # avg_g.__next__() # avg1 = avg_g.send(10) # avg2 = avg_g.send(20) # print(avg1,avg2) 輸出結果: 10.0
yield from: 後接列表、生成器、協程。與asyncio.coroutine同時使用,定義協程函數。在python3.5以後改成了await。當yield from後面是IO耗時操作的時候,會切換至另一個yield from。
在這我們簡單來說:yield可以在函數中代替for循環對返回的數據進行迭代
def generator(): a = 'ab' b = '12' for i in a: yield i for i in b: yield i g = generator() 從生成器中取返回值 for i in g: print(i) 輸出結果: a b 1 2 def generator(): a = 'cd' b = '34' yield from a # ,b只能有一個變量 yield from b g = generator() #從生成器中取返回值 for i in g: print(i) 輸出結果: c d 3 4
回顧:
一、send:
1、send的作用范圍和next一模一樣(從一個yield作用到下一個yield)
2、第一次不能使用send
3、函數在的最後一個yield不能接收新的值
二、
預激生成器的裝飾器的例子
模板:
[每一個元素或者是和元素相關的操作 for 元素 in 可迭代數據類型] 遍歷後挨個處理
[滿足條件的元素進行相關的操作 for 元素 in 可迭代數據類型 if 元素相關的條件] 篩選功能egg_list = ['雞蛋%s'%i for i in range(10)] #這是個列表推導式 print(egg_list) '''相當於''' egg_list = [] for i in range(10): egg_list.append('雞蛋%s' %i) print(egg_list) print([i for i in range(10)]) 輸出結果: ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4', '雞蛋5', '雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9'] ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4', '雞蛋5', '雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9'] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- 生成器表達式(generator expression)也叫生成器推導式或生成器解析式,用法與列表推導式非常相似,在形式上生成器推導式使用圓括號(parentheses)作為定界符,而不是列表推導式所使用的方括號(square brackets)。
- 與列表推導式最大的不同是,生成器推導式的結果是一個生成器對象。生成器對象類似於迭代器對象,具有惰性求值的特點,只在需要時生成新元素,比列表推導式具有更高的效率,空間占用非常少,尤其適合大數據處理的場合。
- 使用生成器對象的元素時,可以根據需要將其轉化為列表或元組,也可以使用生成器對象的next()方法或者內置函數next()進行遍歷,或者直接使用for循環來遍歷其中的元素。但是不管用哪種方法訪問其元素,只能從前往後正向訪問每個元素,不能再次訪問
- 已訪問過的元素,也不支持使用下標訪問其中的元素。當所有元素訪問結束以後,如果需要重新訪問其中的元素,必須重新創建該生成器對象,enumerate、filter、map、zip等其他迭代器對象也具有同樣的特點。
g = (i for i in range(10)) print(g) #返回了一個生成器 for i in g: print(i) 輸出結果: <generator object <genexpr> at 0x000002075E40FC10> 0 1 2 3 4 5 6 7 8 9
例一:將一個字典的key和value對調
dic = {'a' : 97,'b' : 98} dic_exchange = {dic[k] :k for k in dic} print(dic_exchange) 輸出結果: {97: 'a', 98: 'b'}
set = {x**2 for x in [1,-1,2,-2,3,4]} print(set) 輸出結果: {16, 1, 4, 9}
唯獨沒有元組推導式,要得到一個元組就可以直接將推出來的數據類型轉換為元組就行
總結:各種推導式:生成器 列表 字典 集合
1、遍歷操作
2、篩選操作
二、惰性運算
生成器與迭代器都是惰性運算:
但是生成器你可以看的見因為這是你寫的而迭代器一般看不見
1、同一生成器中的數據只能取一次取完就沒了
2、惰性運算:不找它要值它就不返回
# 列表解析
sum([i for i in range(100000000)]) # 內存占用大,機器容易卡死# 生成器表達式
sum(i for i in range(100000000)) # 幾乎不占內存
1.把列表解析的[]換成()得到的就是生成器表達式2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3.Python不但使用迭代器協議,讓for循環變得更加通用。大部分內置函數,也是使用迭代器協議訪問對象的。
感謝各位能夠看到這裡:在魯迅一篇未發表的文章中說過:“代碼看懂了不是懂一定要自己實際操作哇這樣才能更好的理解和吸收。”
最後來一句:一個人可以在任何他懷有無限熱忱的事情上成功,讓我們一起進步吧