python yield
yield用途
return一個generation。關於generation的信息可詳細查看官網。
本文擬解決問題
- yield爲什麼運行速度很快?
- yield的工作原理
- 如何理解yield?
1. yield爲什麼運行速度很快
通過跑python程序,發現運行yield與list,list append、return方法比較,yield的運行速度在某些情況下相對更快速,並且在內存方面,yield較前者內存耗費更小。因爲yield生成的是一個生成器,它不必一次性將數據全部加載到內存中,而是在需要的時候讀取數據,並且數據只迭代一次;另外,yield的底層過程使用C語言封裝,使得其開發效率更加高效。
2. yield的工作原理
- yield與list的工作機制是兩個概念,其中list在python中返回iterable可迭代對象,而yield返回生成器generator對象。生成器對象是一種迭代器iterator,迭代器遵守迭代協議。iterable可以使用python內置的iter()方法返回一個iterator對象。
- iterator對象中包含next()方法,next()方法從迭代器的第一個元素訪問,每次訪問返回當前元素並且指針向前移一步,使用next()方法後數據的讀取不能回退。若在使用迭代對象中下一個元素不存在,繼續使用next()方法會報錯:StopIteration。
- 在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等等。
文章的不足之處,還請提醒,多多關照。