python中yield詳解筆記

python yield

yield用途

return一個generation。關於generation的信息可詳細查看官網。

本文擬解決問題

  1. yield爲什麼運行速度很快?
  2. yield的工作原理
  3. 如何理解yield?

1. yield爲什麼運行速度很快

  通過跑python程序,發現運行yield與list,list append、return方法比較,yield的運行速度在某些情況下相對更快速,並且在內存方面,yield較前者內存耗費更小。因爲yield生成的是一個生成器,它不必一次性將數據全部加載到內存中,而是在需要的時候讀取數據,並且數據只迭代一次;另外,yield的底層過程使用C語言封裝,使得其開發效率更加高效。

2. yield的工作原理

  1. yield與list的工作機制是兩個概念,其中list在python中返回iterable可迭代對象,而yield返回生成器generator對象。生成器對象是一種迭代器iterator,迭代器遵守迭代協議。iterable可以使用python內置的iter()方法返回一個iterator對象。
  2. iterator對象中包含next()方法,next()方法從迭代器的第一個元素訪問,每次訪問返回當前元素並且指針向前移一步,使用next()方法後數據的讀取不能回退。若在使用迭代對象中下一個元素不存在,繼續使用next()方法會報錯:StopIteration。
  3. 在python中,函數中使用yield的數據返回對象就是一個生成器。在調用生成器運行的過程中,每次遇到 yield 時函數會暫停並保存當前所有的運行信息,返回 yield 的值, 並在下一次執行 next() 方法時從當前位置繼續運行。調用一個生成器函數,返回的是一個迭代器對象。

3. 如何理解yield

本節附帶了兩個程序,主要在於能夠對yield有進一步深刻易懂的理解。
(1) yield簡單運行過程
** 此點參照網上博客。**

   def foo():
       print("test...")
       while True:
           res = yield 9
           print("en heng")
           print(res)
   if __name__ == '__main__':    
       g = foo()
       print(g)
       print("1", "*"*20)
       print(next(g))
       print("2", "*"*20)
       print(g.send(7))
       print("3", "*"*20)
       print(next(g))

運行結果:

	<generator object foo at 0x0000023EB0873750>
	1 ********************
	test...
	9
	2 ********************
	en heng
	7
	9
	3 ********************
	en heng
	None
	9

通過上述運行結果,可以觀察到以下三點:

  • 函數帶有yield,返回的是一個generator對象;
  • 帶yield函數的執行順序:
      第一步,從函數開頭執行到yield爲止,可以將當前的yield看做return,將數據“9”返回終端;
      第二步,若yield在循環體內,則從上一次停止位置到到結束部分再到循環體(不會執行循環體以外的程序);若yield不在循環體內,則程序直接執行到函數末尾並且報一個“StopIteration”錯誤(表示生成器元素已經遍歷到末尾);
  • 對生成器使用send()方法時運行流程:一般使用send()方法分兩步執行,①完成賦值,②執行next()方法。在上述程序中,對foo函數調用send()方法(在調用next()方法之後調用send()方法)時,首先完成對左邊變量res的賦值操作,然後執行next()方法。

(2)yield的惰性計算
** 此點參照了網上博客。 **

  生成器表達式的惰性計算。有點類似於Spark中RDD的操作機制,僅當觸發動作類型action操作是纔會執行轉換類型transformation操作(與RDD間的依賴有關)。
  備註:對spark的RDD操作機制感興趣可以在網易雲課堂上廈門大學林子雨老師的課《Spark編程基礎》——免費的。
  生成器的惰性機制:生成器表達式只有在被檢索時候,纔會被賦值。
a. 程序走起

	def add(s, x):
	    return s + x
	
	def gen():
	    for i in range(7):
	        yield i
	if __name__ == "__main__":
		base = gen()
		for n in [1, 100]:
		    print("the n = ", n)
		    base = (add(i, n) for i in base)
		print(base)
		print(list(base))

b. 運行結果展示

	the n =  1
	the n =  100
	<generator object <genexpr> at 0x00000248CA0238B8>
	[200, 201, 202, 203, 204, 205, 206]

c. 爲什麼結果是[200, 201, 202, 203, 204, 205, 206]?

  • 首先需要注意的第一個點:add(i, n)中的n是一個變量而非當時的值。
  • 第二個點:此處有三個生成式表達式。調用gen()的base變量是一個generator,此時並沒有真正觸發迭代式操作(需要調用next()方法觸發);主函數的for循環有兩個generator表達式(add(i, n) for i in base)
  • 第三個點:當運行最後一步print(list(base))時,觸發了迭代式(next()方法)操作,開啓了從第一個generator到第三個generator表達式的計算,注意的是此時的for循環遍歷已經完成,此時的n=10。因此,程序可以寫作:
       def add(s, x):
           return s + x
       def gen():
           for i in range(4):
               yield i  #  第一個管道
       if __name__ == '__main__':
           base = gen()
           base = (add(i, 10) for i in base) #  第二個管道
           base = (add(i, 10) for i in base) #  第三個管道
           list(base) #  開關驅動器
    

以上是目前掌握的yield,關於yield在程序優化中使用較多,同時關於python程序優化的小技巧還有很多,比如enumerate、map、lambda等等。

文章的不足之處,還請提醒,多多關照。

參考:
對 Python 迭代的深入研究
python中yield的用法詳解——最簡單,最清晰的解釋

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章