【Python】迭代器、生成器、yield單執行緒非同步併發實現詳解
大家在學習python開發時可能經常對迭代器、生成器、yield關鍵字用法有所疑惑,在這篇文章將從理論+程式除錯驗證的方式詳細講解這部分知識,話不多說,直接進入主題。
一、迭代器(Iterater):
首先介紹迭代器,迭代器是訪問集合元素的一種方式,迭代器物件從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。是不是覺得跟for迴圈很像?但是迭代器有幾個特性需記住:
1、訪問者不需要關心迭代器內部結構,只需不斷執行next()方法獲取下一個內容;
2、不能隨機訪問集合中的某個值,只能從頭到尾順序的讀取;
3、訪問到一半時不能回退,不能訪問之前的值;
4、適合遍歷很大的資料集合,節省記憶體,提升速度。
根據以上幾個特性應該對迭代器有所瞭解了吧,我這裡再舉一個例子便於加深理解,大家都玩過linux,在linux中有兩個命令,vi和cat,作用都是顯示出檔案的內容,而當一個檔案很大(比如1G)時,使用vi命令開啟檔案則明顯變慢,都有體會吧,會卡很久;而使用cat命令則沒有這個問題,無論多大的檔案,執行cat命令時都會馬上從檔案第一行資料開始列印;其實原因也很簡單,使用vi時需要將整個檔案載入到記憶體中再顯示出來,而使用cat時則從檔案第一行記錄開始逐行的讀取到記憶體中顯示,而已讀取過的內容則直接釋放掉,這樣每次讀取到記憶體中只有一行記錄響應速度當然快了。
其實這裡的cat正是運用了迭代器的思想,迭代器每次順序取集合中的一個值到記憶體,用完即作廢,再取下一個值,對應特性1,對於很大的檔案遍歷則速度很快,對應特性4;則缺點也是明顯的,迭代器不能取集合中間某個值,對應特性2;前一個值讀取完即回收記憶體,所以無法重複讀取,對應特性3;講到這裡大家應該已經能充分理解迭代器的原理了,下面進行程式碼演示:
1、建立一個迭代器:
a = iter(["wang","xuqian","xiaozhuzi"]) # 已建立一個迭代器物件,設定好需要迭代的值
2、遍歷迭代器資料:
print(a.__next__())
print(a.__next__())
print(a.__next__()) # 有三個值,於是執行三次next()方法
3、結果:
wang
xuqian
xiaozhuzi
就是這麼簡單,由於迭代器的特性,我們只能順序依次進行取值,不能像list那樣可以取集合中的任意值,在這裡三個值都取出後如果再執行a.__next__(),則會報錯:“已停止迭代”
Traceback (most recent call last):
print(a.__next__())
StopIteration
二、生成器(Generator)和yield關鍵字:
生成器定義:當一個函式被呼叫時,返回一個迭代器,那麼這個函式就叫做生成器,如果函式中包括yield語法,則這個函式就是一個生成器
yield:效果就是使函式中斷,並儲存中斷的狀態,中斷後,程式碼可以繼續往下執行,過一段時間還可以重新呼叫這個函式,並且可以從上次yield的下面的一句程式碼開始執行;yield可以返回值,也可以接收send來的引數。
生成器和yield的解釋比較抽象難以理解,別急,下面透過除錯程式碼的方式展示他們的用法,以及yield的魅力!~
def cash(account): # 先定義一個函式,接收一個引數
while account > 0: # 當引數>0時
account -= 100 #減去100
yield 100 #中斷函式嗎,返回100
print("又來取錢啦!") #列印
根據生成器的定義,此時cash函式即為一個生成器,作為一個生成器,當被呼叫後產生的生成器物件肯定是支援迭代器介面,也就是生成器返回一個迭代器,下面我們進行驗證:
a = cash(500) # 呼叫生成器,生成迭代器
print(a.__next__()) # 運用迭代器的next()方法列印第一個迭代值
執行結果為:100
以上驗證了呼叫生成器cash後的確生成了一個迭代器,關於迭代器應該沒有什麼疑問了,可以持續next()取值直到 account == 0;而大家現在一定有個疑問,這裡面的yield關鍵字的作用是什麼?為什麼列印結果沒有print("又來取錢啦!")?那麼下面我跑一遍程式,透過結果來分析程式碼執行順序:
def cash(account):
while account > 0:
account -= 100
yield 100
print("從我開始往下執行!") print("程式碼已經執行完啦")
第0次執行我
100
第一次執行我
從我開始往下執行!
程式碼已經執行完啦!
100
從執行結果可以推斷出,當程式進行到a = cash(500) 時,函式並沒有進行呼叫而是繼續往下走列印“第0次執行我”,為什麼?其實正是因為在函式定義的時候,檢測到函式中寫了yield關鍵字,此時這個函式就變成了一個生成器,於是函式暫停執行;當執行到第一個print(a.__next__()) 時,才開始真正的呼叫函式了,函式執行到yield 100 時,根據yield的特性,函式中斷返回100並列印,程式繼續向下執行列印“第一次執行我”;當執行到第二個print(a.__next__()) 時,繼續呼叫函式,根據yield的特性,會接著上一次中斷的位置繼續執行函式,於是列印“從我開始往下執行!”“程式碼已經執行完啦”,而根據函式中的while迴圈,會再次執行迴圈程式碼,直到 yield 100,函式再次中斷返回100並列印。到此,程式執行完畢!
三、yield單執行緒非同步併發實現
根據以上程式演示,大家應該對yield和生成器的特性理解更加深刻了吧,yield打破了常規的程式碼執行順序,甚至能跳出迴圈,而且下一次呼叫函式(生成器)時還可以接著上次的程式碼向下執行,是不是很牛X?那麼,yield的特性到底有什麼用呢?
舉個例子,我去銀行取錢,加入取款額度較大,100萬~,銀行則需要稽核、調配資金等手續,假如需要1天的時間,那麼這一天我都要等在那裡,這顯然是不合理的吧。最好的辦法是我繼續做別的事情,等銀行審批結束後再通知我去取錢。對於我們寫的程式碼也是一樣,由於我們是單執行緒序列執行程式碼,當呼叫一個函式時,如果這個函式遲遲不返回結果怎麼辦?按照之前的思路,那麼程式只能卡死在那裡一直等待,就和去取錢等銀行審批一個道理;而yield的特性,打破了這一約束,使得我們在單執行緒程式設計的過程中,依然可以實現非同步併發!下面再看一段程式碼:
import time
def consumer(name):
print("%s 準備吃燒烤啦!" %name)
while True:
shaokao = yield
print("燒烤%s被%s吃了!" %(shaokao,name))
def producter(name):
c = consumer("A")
c1 = consumer("B")
c.__next__()
c1.__next__()
print("開始生火烤串啦!")
for i in range(3):
time.sleep(1)
print("烤了兩串羊肉")
c.send(i)
c1.send(i)
A 準備吃燒烤啦!
B 準備吃燒烤啦!
開始生火烤串啦!
烤了兩串羊肉
燒烤0被A吃了!
燒烤0被B吃了!
烤了兩串羊肉
燒烤1被A吃了!
燒烤1被B吃了!
烤了兩串羊肉
燒烤2被A吃了!
燒烤2被B吃了!
這裡就實現了非同步併發控制,一個函式和生成器之間呼叫,透過yield實現;這裡面還有個知識點,c.send(i),前面講過yield不僅能返回值,而且還能接收值,在這裡send()大家可以理解為與next()一樣,都是觸發呼叫生成器中的程式碼,但next()可以理解為傳一個空值給yield,send()則可傳一個實際的值給yield。以上程式碼中將i值傳給了生成器consumer中的Yield
一、迭代器(Iterater):
首先介紹迭代器,迭代器是訪問集合元素的一種方式,迭代器物件從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。是不是覺得跟for迴圈很像?但是迭代器有幾個特性需記住:
1、訪問者不需要關心迭代器內部結構,只需不斷執行next()方法獲取下一個內容;
2、不能隨機訪問集合中的某個值,只能從頭到尾順序的讀取;
3、訪問到一半時不能回退,不能訪問之前的值;
4、適合遍歷很大的資料集合,節省記憶體,提升速度。
根據以上幾個特性應該對迭代器有所瞭解了吧,我這裡再舉一個例子便於加深理解,大家都玩過linux,在linux中有兩個命令,vi和cat,作用都是顯示出檔案的內容,而當一個檔案很大(比如1G)時,使用vi命令開啟檔案則明顯變慢,都有體會吧,會卡很久;而使用cat命令則沒有這個問題,無論多大的檔案,執行cat命令時都會馬上從檔案第一行資料開始列印;其實原因也很簡單,使用vi時需要將整個檔案載入到記憶體中再顯示出來,而使用cat時則從檔案第一行記錄開始逐行的讀取到記憶體中顯示,而已讀取過的內容則直接釋放掉,這樣每次讀取到記憶體中只有一行記錄響應速度當然快了。
其實這裡的cat正是運用了迭代器的思想,迭代器每次順序取集合中的一個值到記憶體,用完即作廢,再取下一個值,對應特性1,對於很大的檔案遍歷則速度很快,對應特性4;則缺點也是明顯的,迭代器不能取集合中間某個值,對應特性2;前一個值讀取完即回收記憶體,所以無法重複讀取,對應特性3;講到這裡大家應該已經能充分理解迭代器的原理了,下面進行程式碼演示:
1、建立一個迭代器:
a = iter(["wang","xuqian","xiaozhuzi"]) # 已建立一個迭代器物件,設定好需要迭代的值
2、遍歷迭代器資料:
print(a.__next__())
print(a.__next__())
print(a.__next__()) # 有三個值,於是執行三次next()方法
3、結果:
wang
xuqian
xiaozhuzi
就是這麼簡單,由於迭代器的特性,我們只能順序依次進行取值,不能像list那樣可以取集合中的任意值,在這裡三個值都取出後如果再執行a.__next__(),則會報錯:“已停止迭代”
Traceback (most recent call last):
print(a.__next__())
StopIteration
二、生成器(Generator)和yield關鍵字:
生成器定義:當一個函式被呼叫時,返回一個迭代器,那麼這個函式就叫做生成器,如果函式中包括yield語法,則這個函式就是一個生成器
yield:效果就是使函式中斷,並儲存中斷的狀態,中斷後,程式碼可以繼續往下執行,過一段時間還可以重新呼叫這個函式,並且可以從上次yield的下面的一句程式碼開始執行;yield可以返回值,也可以接收send來的引數。
生成器和yield的解釋比較抽象難以理解,別急,下面透過除錯程式碼的方式展示他們的用法,以及yield的魅力!~
def cash(account): # 先定義一個函式,接收一個引數
while account > 0: # 當引數>0時
account -= 100 #減去100
yield 100 #中斷函式嗎,返回100
print("又來取錢啦!") #列印
根據生成器的定義,此時cash函式即為一個生成器,作為一個生成器,當被呼叫後產生的生成器物件肯定是支援迭代器介面,也就是生成器返回一個迭代器,下面我們進行驗證:
a = cash(500) # 呼叫生成器,生成迭代器
print(a.__next__()) # 運用迭代器的next()方法列印第一個迭代值
執行結果為:100
以上驗證了呼叫生成器cash後的確生成了一個迭代器,關於迭代器應該沒有什麼疑問了,可以持續next()取值直到 account == 0;而大家現在一定有個疑問,這裡面的yield關鍵字的作用是什麼?為什麼列印結果沒有print("又來取錢啦!")?那麼下面我跑一遍程式,透過結果來分析程式碼執行順序:
def cash(account):
while account > 0:
account -= 100
yield 100
print("從我開始往下執行!") print("程式碼已經執行完啦")
a = cash(500)
print("第0次執行我")
print(a.__next__())
print("第一次執行我")
print(a.__next__())執行結果為:
第0次執行我
100
第一次執行我
從我開始往下執行!
程式碼已經執行完啦!
100
從執行結果可以推斷出,當程式進行到a = cash(500) 時,函式並沒有進行呼叫而是繼續往下走列印“第0次執行我”,為什麼?其實正是因為在函式定義的時候,檢測到函式中寫了yield關鍵字,此時這個函式就變成了一個生成器,於是函式暫停執行;當執行到第一個print(a.__next__()) 時,才開始真正的呼叫函式了,函式執行到yield 100 時,根據yield的特性,函式中斷返回100並列印,程式繼續向下執行列印“第一次執行我”;當執行到第二個print(a.__next__()) 時,繼續呼叫函式,根據yield的特性,會接著上一次中斷的位置繼續執行函式,於是列印“從我開始往下執行!”“程式碼已經執行完啦”,而根據函式中的while迴圈,會再次執行迴圈程式碼,直到 yield 100,函式再次中斷返回100並列印。到此,程式執行完畢!
三、yield單執行緒非同步併發實現
根據以上程式演示,大家應該對yield和生成器的特性理解更加深刻了吧,yield打破了常規的程式碼執行順序,甚至能跳出迴圈,而且下一次呼叫函式(生成器)時還可以接著上次的程式碼向下執行,是不是很牛X?那麼,yield的特性到底有什麼用呢?
舉個例子,我去銀行取錢,加入取款額度較大,100萬~,銀行則需要稽核、調配資金等手續,假如需要1天的時間,那麼這一天我都要等在那裡,這顯然是不合理的吧。最好的辦法是我繼續做別的事情,等銀行審批結束後再通知我去取錢。對於我們寫的程式碼也是一樣,由於我們是單執行緒序列執行程式碼,當呼叫一個函式時,如果這個函式遲遲不返回結果怎麼辦?按照之前的思路,那麼程式只能卡死在那裡一直等待,就和去取錢等銀行審批一個道理;而yield的特性,打破了這一約束,使得我們在單執行緒程式設計的過程中,依然可以實現非同步併發!下面再看一段程式碼:
import time
def consumer(name):
print("%s 準備吃燒烤啦!" %name)
while True:
shaokao = yield
print("燒烤%s被%s吃了!" %(shaokao,name))
def producter(name):
c = consumer("A")
c1 = consumer("B")
c.__next__()
c1.__next__()
print("開始生火烤串啦!")
for i in range(3):
time.sleep(1)
print("烤了兩串羊肉")
c.send(i)
c1.send(i)
producter("jiaqi")
根據前面講的知識,應該可以得出執行結果:A 準備吃燒烤啦!
B 準備吃燒烤啦!
開始生火烤串啦!
烤了兩串羊肉
燒烤0被A吃了!
燒烤0被B吃了!
烤了兩串羊肉
燒烤1被A吃了!
燒烤1被B吃了!
烤了兩串羊肉
燒烤2被A吃了!
燒烤2被B吃了!
這裡就實現了非同步併發控制,一個函式和生成器之間呼叫,透過yield實現;這裡面還有個知識點,c.send(i),前面講過yield不僅能返回值,而且還能接收值,在這裡send()大家可以理解為與next()一樣,都是觸發呼叫生成器中的程式碼,但next()可以理解為傳一個空值給yield,send()則可傳一個實際的值給yield。以上程式碼中將i值傳給了生成器consumer中的Yield
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29018063/viewspace-2079767/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- java多執行緒詳解(併發,並行,同步)Java執行緒並行
- 程式執行緒、同步非同步、阻塞非阻塞、併發並行執行緒非同步並行
- 併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒非同步執行緒
- 非同步/同步,阻塞/非阻塞,單執行緒/多執行緒概念梳理非同步執行緒
- 啃碎併發(六):Java執行緒同步與實現Java執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- 程式與執行緒、同步與非同步、阻塞與非阻塞、併發與並行執行緒非同步並行
- Java併發和多執行緒4:使用通用同步工具CountDownLatch實現執行緒等待Java執行緒CountDownLatch
- 【java 多執行緒】多執行緒併發同步問題及解決方法Java執行緒
- 多執行緒與併發----Semaphere同步執行緒
- 簡單瞭解一下php的迭代生成器yieldPHP
- java併發程式設計 | 執行緒詳解Java程式設計執行緒
- 多執行緒併發安全問題詳解執行緒
- 用多執行緒,實現併發,TCP執行緒TCP
- 詳解Python中yield生成器的用法Python
- 多執行緒併發同步問題及解決方案執行緒
- python基礎執行緒-管理併發執行緒Python執行緒
- JAVA多執行緒詳解(3)執行緒同步和鎖Java執行緒
- java併發程式設計——執行緒同步Java程式設計執行緒
- java執行緒中yield(),sleep(),wait()區別詳解Java執行緒AI
- Java 併發程式設計 | 執行緒池詳解Java程式設計執行緒
- 3種方式實現python多執行緒併發處理Python執行緒
- Flutter非同步與執行緒詳解Flutter非同步執行緒
- Java同步之執行緒池詳解Java執行緒
- Java中的執行緒同步詳解Java執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- 詳解python三大器——迭代器、生成器、裝飾器Python
- Python多執行緒併發的簡單測試Python執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- nodejs 單執行緒 高併發NodeJS執行緒
- Java多執行緒/併發11、執行緒同步通訊:notify、waitJava執行緒AI
- Python迭代和迭代器詳解Python
- 多執行緒系列(十五) -常用併發工具類詳解執行緒
- 多執行緒系列(十六) -常用併發原子類詳解執行緒
- 執行緒,程式,協程, 併發,並行,同步,非同步概念解析執行緒並行非同步
- 多執行緒(五)---執行緒的Yield方法執行緒
- Python簡單實現多執行緒例子Python執行緒
- shell佇列實現執行緒併發控制(轉)佇列執行緒