日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python3之协程(2)---yield与send实现协程操作

發布時間:2024/4/11 python 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python3之协程(2)---yield与send实现协程操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文鏈接:https://www.cnblogs.com/vipchenwei/p/7049404.html

yield與send實現協程操作

之前我們說過,在函數內部含有yield語句即稱為生成器。

下面,我們來看看在函數內部含有yield語句達到的效果。首先,我們來看看以下代碼:

def foo():while True:x = yieldprint("value:",x)g = foo() # g是一個生成器 next(g) # 程序運行到yield就停住了,等待下一個next g.send(1) # 我們給yield發送值1,然后這個值被賦值給了x,并且打印出來,然后繼續下一次循環停在yield處 g.send(2) # 同上 next(g) # 沒有給x賦值,執行print語句,打印出None,繼續循環停在yield處

我們都知道,程序一旦執行到yield就會停在該處,并且將其返回值進行返回。上面的例子中,我們并沒有設置返回值,所有默認程序返回的是None。我們通過打印語句來查看一下第一次next的返回值:

print(next(g))####輸出結果##### None

正如我們所說的,程序返回None。接著程序往下執行,但是并沒有看到next()方法。為什么還會繼續執行yield語句后面的代碼呢?這是因為,send()方法具有兩種功能:第一,傳值,send()方法,將其攜帶的值傳遞給yield,注意,是傳遞給yield,而不是x,然后再將其賦值給x;第二,send()方法具有和next()方法一樣的功能,也就是說,傳值完畢后,會接著上次執行的結果繼續執行,知道遇到yield停止。這也就為什么在調用g.send()方法后,還會打印出x的數值。有了上面的分析,我們可以很快知道,執行了send(1)后,函數被停止在了yield處,等待下一個next()的到來。程序往下執行,有遇到了send(2),其執行流程與send(1)完全一樣。

  有了上述的分析,我們可以總結出send()的兩個功能:1.傳值;2.next()。

  既然send()方法有和next一樣的作用,那么我們可不可以這樣做:

def foo():while True:x = yieldprint("value:",x)g = foo() g.send(1) #執行給yield傳值,這樣行不行呢?

 很明顯,是不行的。

TypeError: can't send non-None value to a just-started generator

錯誤提示:不能傳遞一個非空值給一個未啟動的生成器。

  也就是說,在一個生成器函數未啟動之前,是不能傳遞數值進去。必須先傳遞一個None進去或者調用一次next(g)方法,才能進行傳值操作。至于為什么要先傳遞一個None進去,可以看一下官方說法。

Because generator-iterators begin execution at the top of thegenerator's function body, there is no yield expression to receivea value when the generator has just been created. Therefore,calling send() with a non-None argument is prohibited when thegenerator iterator has just started, and a TypeError is raised ifthis occurs (presumably due to a logic error of some kind). Thus,before you can communicate with a coroutine you must first callnext() or send(None) to advance its execution to the first yieldexpression.

問題就來,既然在給yield傳值過程中,會調用next()方法,那么是不是在調用一次函數的時候,是不是每次都要給它傳遞一個空值?有沒有什么簡便方法來解決這個問題呢?答案,裝飾器!!看下面代碼:

def deco(func): # 裝飾器:用來開啟協程def wrapper():res = func()next(res)return res # 返回一個已經執行了next()方法的函數對象return wrapper @deco def foo():pass

上面我yield是沒有返回值的,下面我們看看有返回值的生成器函數。

def deco(func):def wrapper():res = func()next(res)return resreturn wrapper @deco def foo():food_list = []while True:food = yield food_list #返回添加food的列表food_list.append(food)print("elements in foodlist are:",food) g = foo() print(g.send('蘋果')) print(g.send('香蕉')) print(g.send('菠蘿')) ###########輸出結果為###### elements in foodlist are: 蘋果 ['蘋果'] elements in foodlist are: 香蕉 ['蘋果', '香蕉'] elements in foodlist are: 菠蘿 ['蘋果', '香蕉', '菠蘿']

分析:首先,我們要清楚,在函數執行之前,已經執行了一次next()(裝飾器的功能),程序停止yield。接著程序往下執行,遇到g.send(),然后將其值傳遞給food,然后再將獲得的food添加到列表food_list中。打印出food,再次循環程序停在yield。程序繼續執行,又遇到g.send(),其過程與上面是一模一樣的。看看以下的程序執行流程,你可能會更清楚。

?

  這里我們要明確一點,yield的返回值和傳給yield的值是兩碼事!!

  yiedl的返回值就相當于return的返回值,這個值是要被傳遞出去的,而send()傳遞的值,是要被yield接受,供函數內部使用的的,明確這一點很重要的。那么上面的打印,就應該打印出yield的返回值,而傳遞進去的值則本保存在一個列表中。

三、實例

"""模擬:grep -rl 'root' /etc""" import os def deco(func): # 用來開啟協程def wrapper(*args,**kwargs):res = func(*args,**kwargs)next(res) # res.seng(None)return resreturn wrapper @deco def search(target):while True:PATH = yieldg = os.walk(PATH) # 獲取PATH目錄下的文件,文件夾for par_dir, _, files in g: #迭代解包,取出當前目錄路徑和文件名for file in files:file_path = r'%s\%s' %(par_dir,file) # 拼接文件的絕對路徑target.send(file_path) # 給下一個 @deco def opener(target, pattern=None):while True:file_path = yieldwith open(file_path, encoding='utf-8') as f:target.send((file_path, f)) # 將文件路徑和文件對象一起傳遞給下一個函數的yield,因為在打印路徑時候,需要打印出文件路徑,只有從這里傳遞下去 @deco def cat(target):while True:filepath, f = yield # 這里接收opener傳遞進來的路徑和文件對象for line in f:tag = target.send((filepath, line)) # 同樣,也要傳遞文件路徑,并且獲取下一個函數grep的返回值,從而判斷該文件是否重復讀取了if tag: # 如果為真,說明該文件讀取過了,則執行退出循環break @deco def grep(target, pattern):tag = Falsewhile True:filepath, line = yield tag # 接受兩個值,并且設置返回值,這個返回值要傳遞給發送消息的send(),也就是cat()函數sendtag = Falseif pattern in line: # 如果待匹配字符串在該行target.send(filepath) # 把文件路徑傳遞給pritertag = True # 設置tag @deco def printer():while True:filename = yieldprint(filename)

調用方式:

PATH1 = r'D:\CODE_FILE\python\test' search(opener(cat(grep(printer(), 'root')))).send(PATH1)

輸出結果:

######找出了含有'root'的所有文件####### D:\CODE_FILE\python\test\a.txt D:\CODE_FILE\python\test\test1\c.txt D:\CODE_FILE\python\test\test1\test2\d.txt

程序分析:

?  有了上面的基礎,我們來分析一下上述程序的執行。

  每一個函數之前都有一個@deco裝飾器,這個裝飾器用于開啟協程。首先我們定義了一個search(),其內部有關鍵字yield,則search()是一個生成器。也就是說,我們可以通過send()給其傳遞一個值進去。search()函數的功能 是:獲取一個文件的絕對路徑,并將這個絕對路徑通過send()方法,在傳遞給下個含有yield的函數,也就是下面的opener函數。opener的yield接受了search()傳遞進來的路徑,并將其賦值給了file_path,然后我們根據這個路徑,打開了一個文件,獲取了一個文件對象f。然后我們在將這個文件對象send()給cat()函數,這個函數功能是讀取文件中的內容,我們根據逐行讀取文件內容,將每次讀取到的內容,在send()給下一個函數,也就是grep(),這個函數功能是實現過濾操作。我們從上一個函數cat()接受到的每一行內容,在grep()函數里面進行過濾處理,如果有匹配到的過濾內容,那么我們將就過濾到的文件傳遞給下一個函數print(),該函數主要是打印出文件路徑。也許,上述的描述內容你沒看懂,下面看看這個程序的流程圖:

根據上述流程,我們很清楚知道,send()傳遞給下一個函數的值。但是上述代碼存在一個問題:如果待過濾的字符在一個文件中存在多個,而在讀取文件的時候,我們是一行一行地讀取,然后再傳遞給下一個函數。我們的目的是:過濾出包好pattern的文件,如果一個文件存在多個同樣的pattern,那么就會輸出多次同樣的文件名。這無疑是浪費內存,要解決這中問題,我們可以通過yield的返回值來控制,每次讀取時候的條件。具體實施,看上述代碼注釋。

?

?

?

?

超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

總結

以上是生活随笔為你收集整理的python3之协程(2)---yield与send实现协程操作的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。