神奇的Python生成表示式(僅測試,無理論依據,但結論可靠)
- 某部落格闡述生成器
在Python中,擁有這種能力的“函式”被稱為生成器,它非常的有用。生成器(以及yield語句)最初的引入是為了讓程
序員可以更簡單的編寫用來產生值的序列的程式碼。 以前,要實現類似隨機數生成器的東西,需要實現一個類或者一個模組,在生成資料的同時保持對每次呼叫之間狀態的跟蹤。引入生成器之後,這變得非常簡單。
為了更好的理解生成器所解決的問題,讓我們來看一個例子。在瞭解這個例子的過程中,請始終記住我們需要解決的問題:生成值的序列。
注意:在Python之外,最簡單的生成器應該是被稱為協程(coroutines)的東西。在本文中,我將使用這個術語。請記住,在Python的概念中,這裡提到的協程就是生成器。Python正式的術語是生成器;協程只是便於討論,在語言層面並沒有正式定義。
python物件有兩個重要的器,一個是迭代器一個是生成器,自打學python開始就不斷接觸兩者,可從來理解都是生成器區別於迭代只不過前者無實際執行而是每次呼叫的時候遍歷一個值並指標停留該處,於是乎自然而然一直是這個想法,自從一次使用。。。。
- if else 結構 選擇使用生成器:
def genforbrow(obj,browlen, fileORvar):
if fileORvar =="var":
print("var")
for index in range(0,len(obj)-1,browlen):
if index>len(obj):
print("no data left")
else:
yield obj[index:(index+browlen)]
elif fileORvar == "file":
print("pandas")
return pd.read_csv(obj, iterator= True, chunksize= browlen)
else:
print("no parameter called: %s"%fileORvar)
使用
kk = genforbrow("train.csv", 2,"file")
kk
Out[7]: <generator object genforbrow at 0x000000A842058308>
咦!!!什麼情況??? 我的fileORvar 傳入引數是 “file”為何解僱是 generator?那段程式碼按道理應該不執行才對,我們單獨試試?
In [8]: def something(obj,browlen):
...: return pd.read_csv(obj, iterator= True, chunksize= browlen)
...:
In [9]:gg = something("train.csv",5)
In [9]:gg
Out[9]: <pandas.io.parsers.TextFileReader at 0xa8420612e8>
額,單獨執行就可以返回pandas物件,好奇怪,好吧,再做一次簡單的實驗。。。
def test_yield(n):
kk =[]
if n<5:
for i in range(n):
kk.append(i)
elif n<10:
for i in range(n):
kk.append(i)
elif n<20:
for i in range(n):
kk.append(i)
elif n<30:
for i in range(n):
kk.append(i)
else:
for i in range(n):
yield kk.append(i)
yield kk
return kk
執行
In [11]: test = test_yield(40)
In [12]: next(test)
In [13]: next(test)
Out[13]: [0]
In [14]: next(test)
In [15]: next(test)
Out[15]: [0, 1]
In [16]: next(test)
###沒有返回值
In [17]: next(test)
Out[17]: [0, 1, 2]
In [18]: next(test)
###沒有返回值
In [19]: next(test)
Out[19]: [0, 1, 2, 3]
規律出來了吧?看起來yield就是一個暫定器,迭代被暫停了,只有你再呼叫的時候才會執行,等於就是一個卡頓的執行過程;那下面這種情況呢?我們把n值改成5?
In [20]: test = test_yield(5)
In [21]: next(test)
StopIterationTraceback (most recent call last)
<ipython-input-21-911ea584f8be> in <module>()
----> 1 next(test)
StopIteration: [0, 1, 2, 3, 4]
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
一臉懵B,什麼情況,居然報錯了。。。簡直,要不要人活了,不是說是個暫定作用的麼?一下子跑出來輸出[0,1,2,3,4]才對的呢。。。 還以為懂了呢。。。你大爺的,好吧重點來了!!!
- 什麼是生成器?
我們回過頭來比劃比劃那張圖
圖在說什麼:表示式是生成器,生成式函式是生成器,生成器是迭代器,list,set,dict等等是容器,容器是可迭代的,對容器進行迭代生成迭代器(總之來說生成器有個好處就是,不需要容器儲存資料,不用的時候不執行,省記憶體,省CPU):::暈,什麼亂七八糟的,看個例子,理解下迭代過程
from itertools import count
counter = count(start=10)
next(counter)
Out[3]: 10
next(counter)
Out[4]: 11
next(counter)
Out[4]: 11
itertools這貨先是生成一個迭代器,然後next方法呼叫迭代器,哦,原來迭代是要有生成者和使用者的呢,而迭代器是通過next方法呼叫的呢(PS,這貨很強大,可以偷著用→貨源)
嗚,再加一個例子理解下
In [57]: x = [1, 2, 3]
In [58]: y = iter(x)
In [59]: z = iter(x)
In [60]: next(y)
Out[60]: 1
In [61]: next(y)
Out[61]: 2
In [62]: next(y)
Out[62]: 3
In [63]: next(z)
Out[63]: 1
In [64]: next(z)
Out[64]: 2
In [65]: next(y)
StopIterationTraceback (most recent call last)
<ipython-input-65-714521682ae9> in <module>()
----> 1 next(y)
StopIteration:
唔,我什麼也不說,只告訴你StopIteration,說明迭代器用乾淨了,沒了。額,也大概理解了。1、迭代是next呼叫iter(迭代器)的過程 ;2、 容器是可迭代的,但是得用東西把他改造好了,名正言順的迭代器,然後再迭代;3, 生成器是迭代器,但是生成器好像更懶。
可是,還是沒理解yield一下函式就變生成器了。。。沒辦法,我只能搬出官方的東西,別怪我
翻了下官方文件,給出了這麼一段話
Using a yield expression in a function’s body causes that function to be a generator.
注意,” in a function’s body”,什麼問題,就是說,你函式主體如果包含了yield,那你的函式就變成了一個生成器了。而第一次呼叫的時候函式內部程式碼不執行,而返回了這個生成器,當你使用for進行迭代的時候.第一次迭代中你的函式會執行,直到碰到 yield 關鍵字,然後返回 yield 後的值作為第一次迭代的返回值。等等,for是幹嘛的?.
找了個例子,不好解釋,大家自己看(然而還是大概:for 無非說了:給一個可迭代物件,我不斷的獲取next方法,迭代迭代迭代)
In [66]: import dis
In [67]: x = [1, 2, 3]
In [68]: dis.dis('for _ in x: pass')
1 0 SETUP_LOOP 14 (to 17)
3 LOAD_NAME 0 (x)
6 GET_ITER
>> 7 FOR_ITER 6 (to 16)
10 STORE_NAME 1 (_)
13 JUMP_ABSOLUTE 7
>> 16 POP_BLOCK
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
那上面那個例子yield的值是哪個值呢?很明顯就是else部分咯。
else:
for i in range(n):
yield kk.append(i)
yield kk
然而,yield不是把整個函式物件都變成生辰器了麼?那?簡單的說yield返回的是地址,每次迭代使用next方法時作用於在最後那個迴圈,這個for上面的for的迭代器已經消耗並不返回,這個地址也無從迭代可言,反正反正反正,只要有yield(爺)在,你函式就得跟老子姓“生成”,至於你們生死我不管,我只管我待的地方,聽起來有點triky,我們下面幾個例子依次說明
- 第一次函式呼叫不執行
- 如果yield存在,yield以上程式碼都執行;yield上的程式碼只管跑,不返回,即使碰到return也不;不過return能終止迭代,但不妨礙生成器生成,不返回值
- 程式碼止於yield
(隱藏一個知識點,生成器是迭代器,迭代器用next方法執行)
In [33]: def yield_return():
...: return 1
...: yield 2
...: yield 3
...: yield 4
...: return 5
...:
In [34]: yield_return()
Out[34]: <generator object yield_return at 0x000000A842058360>
似乎程式碼看起來第一個ruturn應該就結束掉程式碼了?可是。。記住第一次並不執行
In [36]: next(test_yield)
TypeErrorTraceback (most recent call last)
<ipython-input-36-e17ab9b35113> in <module>()
----> 1 next(test_yield)
TypeError: 'function' object is not an iterator
我們改下程式碼
In [41]: test = yield_return()
In [42]: next(test)
Out[42]: 2
In [43]: next(test)
Out[43]: 3
In [44]: next(test)
Out[44]: 4
In [45]: next(test)
StopIterationTraceback (most recent call last)
<ipython-input-45-911ea584f8be> in <module>()
----> 1 next(test)
StopIteration: 5
發現return作用已經喪失,程式碼止於yield,最後一個說明
In [48]: def yield_return():
...: for i in range(3):
...: print(i)
...: yield 2
...: yield 3
...: yield 4
...: return 5
...:
In [49]: test = yield_return()
In [50]: next(test)
0
1
2
Out[50]: 2
In [51]: next(test)
Out[51]: 3
In [52]: next(test)
Out[52]: 4
In [53]: next(test)
StopIterationTraceback (most recent call last)
<ipython-input-53-911ea584f8be> in <module>()
----> 1 next(test)
StopIteration: 5
這個例子很顯著,生成器程式碼包含了前面迭代程式碼,但是該程式碼不屬於迭代物件, 且無法使用return返回值,使用return只會導致報錯
附加幾個連結