python函數語言程式設計之yield表示式形式

任平生78發表於2018-03-23

先來看一個例子

def foo():
    print("starting...")
    while True:
        res = yield
        print("res:",res)

g = foo()
next(g)

在上面的例子裡,因為foo函式中有yield關鍵字,所以foo()函式的執行結果g是一個生成器,此時可以使用next(g)或者g.__next__()方法觸發生成器的執行

程式的執行結果為

starting...

使用next(g)觸發生成器的執行時,程式會按照正常的順序從上向下執行,遇到yield,程式就會暫停
並把yield後面所接的值返回

列印next(g)的執行結果

def foo():
    print("starting...")
    while True:
        res = yield
        print("res:",res)

g = foo()
print(next(g))

程式執行結果

starting...
None

在上面的例子裡,執行一次next(g)方法,程式暫停在yield那一行,此時再次呼叫next(g),程式會從yield語句那一行繼續向下執行

修改上面的程式碼,多呼叫幾次next方法,並列印next方法的返回結果

def foo():
    print("starting...")
    while True:
        res = yield
        print("res:",res)

g = foo()
print(next(g))
print("*"*20)
print(next(g))

上面這段程式碼的執行結果為

starting...
None
********************
res: None
None

可以看到,程式確實按猜想的步驟執行,但是上面的程式也有一個很明顯的缺點:那就是上面的程式碼沒有任何的實際意義:res的值永遠為None

在實際的開發中,使用yield表示式形式的目的是yield可以得到一個值,然後yield把這個值賦值給某個變數,這樣才有實際意義

那應該怎麼操作才能為res變數賦一個值呢??那就是呼叫生成器自身的send方法

send方法可以觸發一次生成器執行,同時還可以把send方法的引數傳遞給yield

修改上面的程式碼

def foo():
    print("starting...")
    while True:
        res = yield
        print("res:",res)

g = foo()
next(g)
print(g.send(5))

程式的執行結果為:

starting...
res: 5
None

來分析一下上面的程式碼的執行過程 :

1.程式開始執行以後,因為foo函式中有yield關鍵字,所以foo函式並不會真的執行,而是先得到一個生成器g.
2.直到呼叫next方法,foo函式正式開始執行,先執行foo函式中的print方法,然後進入while迴圈
3.程式遇到yield關鍵字,程式暫停,此時next(g)語句執行完成
4.程式執行g.send(5),程式會從yield關鍵字那一行繼續向下執行,send會把5這個值傳遞給yield
5.yield接收到send方法傳遞過來的值,然後由yield賦值給res變數
6.由於send方法中包含next()方法,所以程式會繼續向下執行執行print方法,然後再次進入while迴圈
7.程式執行再次遇到yield關鍵字,yield會返回後面的值,由於yield後面沒有接任何引數,所以yield會返回None,程式再次暫停,直到再次呼叫next方法或send方法

修改程式碼,多次呼叫send方法

def foo():
    print("starting...")
    while True:
        res = yield
        print("res:",res)

g = foo()
next(g)
print(g.send(5))
print("*"*20)
print(g.send(10))
print("#"*20)
print(g.send(15))

執行程式,得到如下結果

starting...
res: 5
None
********************
res: 10
None
####################
res: 15
None

可以看到,上面程式碼的執行過程如同上面的分析的執行過程一樣執行

在上面的例子裡,如果呼叫send方法時,傳遞的引數為None,得到的結果會是怎麼樣的呢??

從上面的分析中,可以知道:

如果`g.send()`方法傳送給yield關鍵字的引數為None,則yield關鍵字傳遞給res變數的值就為None
由於yield後面本來沒有接任何值,所以yield返回的值預設也為None,所以程式執行結果會得到兩個None

修改程式碼,驗證上面的猜想

def foo():
    print("starting...")
    while True:
        res = yield
        print("res:",res)

g = foo()
next(g)
print("#"*20)
print(g.send(None))

檢視程式的執行結果

starting...
####################
res: None
None

從程式的執行結果可以看出,如果呼叫生成器的send方法時,傳遞的引數為None,則程式執行的結果將會是兩個None

使用yield表示式形式實現linux系統中的"grep -rl root /etc"命令

程式碼如下:

import os

def init(func):
    def wrapper(*args, **kwargs):
        g = func(*args, **kwargs)
        next(g)
        return g
    return wrapper

@init
def get_file_path(target):
    """
    get file abspath
    # 階段一:遞迴找檔案的絕對路徑,把檔案的完事路徑傳送給階段二
    :param target:
    :return:
    """
    while True:
        start_path = yield
        g = os.walk(start_path)
        for parent_dir, _, files in g:
            for file in files:
                file_path = r"%s\%s" % (parent_dir, file)
                target.send(file_path)

@init
def opener(target):
    """
    get file obj
    # 階段二:收到檔案的完整路徑,開啟檔案獲取檔案物件,把檔案物件傳送給階段三
    :param target:
    :return:
    """
    while True:
        file_path = yield
        with open(file_path, encoding=`utf-8`) as f:
            target.send((file_path, f))

@init
def cat_file(target):
    """
    read file content
    # 階段三:收到檔案物件,for迴圈讀取檔案的每一行內容,把每一行內容發給階段四
    :param target:
    :return:
    """
    while True:
        file_path, f = yield
        for line in f:
            file_content = target.send((file_path, line))
            if file_content:
                break

@init
def grep(target, pattern):
    """
    grep function
    # 階段四:收到檔案的一行內容,判斷要查詢的內容是否在這一行中,如果在,則把檔名傳送給階段五
    :param target:
    :param pattern:
    :return:
    """
    tag = False
    while True:
        file_path, line = yield tag
        tag = False
        if pattern in line:
            target.send(file_path)
            tag = True

@init
def printer():
    """
    print file name
    # 階段五:收到檔名,列印結果
    :return:
    """
    while True:
        filename = yield
        print(filename)

path1 = "/root"         # 定義要搜尋的路徑
path2 = "/etc"          # 定義要搜尋的路徑

g = get_file_path(opener(cat_file(grep(printer(), "root"))))
print(g)
g.send(path1)
g.send(path2)


相關文章