一、協程
1.1協程的概念
協程,又稱微執行緒,纖程。英文名Coroutine。一句話說明什麼是執行緒:協程是一種使用者態的輕量級執行緒。(其實並沒有說明白~)
那麼這麼來理解協程比較容易:
  執行緒是系統級別的,它們是由作業系統排程;協程是程式級別的,由程式設計師根據需要自己排程。我們把一個執行緒中的一個個函式叫做子程式,那麼子程式在執行過程中可以中斷去執行別的子程式;別的子程式也可以中斷回來繼續執行之前的子程式,這就是協程。也就是說同一執行緒下的一段程式碼執行著執行著就可以中斷,然後跳去執行另一段程式碼,當再次回來執行程式碼塊的時候,接著從之前中斷的地方開始執行。
比較專業的理解是:
  協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧。因此:協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每次過程重入時,就相當於進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置。
1.2 協程的優缺點
協程的優點:
  (1)無需執行緒上下文切換的開銷,協程避免了無意義的排程,由此可以提高效能(但也因此,程式設計師必須自己承擔排程的責任,同時,協程也失去了標準執行緒使用多CPU的能力)
  (2)無需原子操作鎖定及同步的開銷
  (3)方便切換控制流,簡化程式設計模型
   (4)高併發+高擴充套件性+低成本:一個CPU支援上萬的協程都不是問題。所以很適合用於高併發處理。
協程的缺點:
  (1)無法利用多核資源:協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和程式配合才能執行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  (2)進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程式
2、Python中如何實現協程
2.1 yield實現協程
  前文所述“子程式(函式)在執行過程中可以中斷去執行別的子程式;別的子程式也可以中斷回來繼續執行之前的子程式”,那麼很容易想到Python的yield,顯然yield是可以實現這種切換的。

def eater(name):
    print("%s eat food" %name)
    while True:
        food = yield
    print("done")
g = eater("gangdan")
print(g)

執行結果:

<generator object eater at 0x0000000002140FC0>

由執行結果可以證明g現在就是生成器函式

2.2 協程函式賦值過程
用的是yield的表示式形式,要先執行next(),讓函式初始化並停在yield,然後再send() ,send會在觸發下一次程式碼的執行時,給yield賦值
next()和send() 都是讓函式在上次暫停的位置繼續執行,

def creater(name):
    print(`%s start to eat food` %name)
    food_list = []
    while True:
        food = yield food_list
        print(`%s get %s ,to start eat` %(name,food))
        food_list.append(food)
# 獲取生成器
builder = creater(`tom`)
# 現在是執行函式,讓函式初始化
next(builder)
print(builder.send(`包子`))
print(builder.send(`骨頭`))
print(builder.send(`菜湯`))

執行結果:
tom start to eat food
tom get 包子 ,to start eat
[`包子`]
tom get 骨頭 ,to start eat
[`包子`, `骨頭`]
tom get 菜湯 ,to start eat
[`包子`, `骨頭`, `菜湯`]

需要注意的是每次都需要先執行next()函式,讓程式停留在yield位置。
如果有多個這樣的函式都需要執行next()函式,讓程式停留在yield位置。為了防止忘記初始化next操作,需要用到裝飾器來解決此問題

def init(func):
    def wrapper(*args,**kwargs):
        builder = func(*args,**kwargs)
        next(builder)    # 這個地方是關鍵可以使用builder.send("None"),第一次必須傳入None。
        return builder
    return wrapper
@init
def creater(name):
    print(`%s start to eat food` %name)
    food_list = []
    while True:
        food = yield food_list
        print(`%s get %s ,to start eat` %(name,food))
        food_list.append(food)
# 獲取生成器
builder = creater("tom")
# 現在是直接執行函式,無須再函式初始化
print(builder.send(`包子`))
print(builder.send(`骨頭`))
print(builder.send(`菜湯`))

執行結果:

tom start to eat food
tom get 包子 ,to start eat
[`包子`]
tom get 骨頭 ,to start eat
[`包子`, `骨頭`]
tom get 菜湯 ,to start eat
[`包子`, `骨頭`, `菜湯`]


2.3 協程函式簡單應用

請給Tom投餵食物

def init(func):
    def wrapper(*args,**kwargs):
        builder = func(*args,**kwargs)
        next(builder)
        return builder
    return wrapper
@init
def creater(name):
    print(`%s start to eat food` %name)
    food_list = []
    while True:
        food = yield food_list
        print(`%s get %s ,to start eat` %(name,food))
        food_list.append(food)
def food():
    builder = creater("Tom")
    while True:
        food = input("請給Tom投餵食物:").strip()
        if food == "q":
            print("投喂結束")
            return 0
        else:
            builder.send(food)
if __name__ == `__main__`:
    food()

執行結果:

Tom start to eat food
請給Tom投餵食物:骨頭
Tom get 骨頭 ,to start eat
請給Tom投餵食物:菜湯
Tom get 菜湯 ,to start eat
請給Tom投餵食物:q
投喂結束

2.4 協程函式的應用
實現linux中”grep -rl error <目錄>”命令,過濾一個檔案下的子檔案、字資料夾的內容中的相應的內容的功能程式
首先了解一個OS模組中的walk方法,能夠把引數中的路徑下的資料夾開啟並返回一個元組

>>> import os # 匯入模組
>>> os.walk(r"E:Pythonscript") #使用r 是讓字串中的符號沒有特殊意義,針對的是轉義
<generator object walk at 0x00000000035D3F10>
>>> g = os.walk(r"E:Pythonscript")
>>> next(g)
(`E:\Python\script`, [`.idea`, `函式`], [])

返回的是一個元組,第一個元素是檔案的路徑,第二個是資料夾,第三個是該路徑下的檔案
這裡需要用到一個寫程式的思想:程式導向程式設計
二、程式導向程式設計
程式導向:核心是過程二字,過程及即解決問題的步驟,基於程式導向設計程式就是一條工業流水線,是一種機械式的思維方式。流水線式的程式設計思想,在設計程式時,需要把整個流程設計出來
優點:
1:體系結構更加清晰
2:簡化程式的複雜度
缺點:
可擴充套件性極其的差,所以說程式導向的應用場景是:不需要經常變化的軟體,如:linux核心,httpd,git等軟體

下面就根據程式導向的思想完成協程函式應用中的功能
目錄結構:

test
├── aa
│   ├── bb1
│    │    └── file2.txt
│   └── bb2
│       └── file3.txt
└─ file1.txt
檔案內容:
file1.txt:error123
file2.txt:123
file3.txt:123error

程式流程
    第一階段:找到所有檔案的絕對路徑
    第二階段:開啟檔案
    第三階段:迴圈讀取每一行
    第四階段:過濾“error”
    第五階段:列印該行屬於的檔名

第一階段:找到所有檔案的絕對路徑
g是一個生成器,就能夠用next()執行,每次next就是執行一次,這裡的執行結果是依次開啟檔案的路徑

>>> import os
>>> g = os.walk(r"E:Pythonscript函式	est")
>>> next(g)
(`E:\Python\script\函式\test`, [`aa`], [])
>>> next(g)
(`E:\Python\script\函式\test\aa`, [`bb1`, `bb2`], [`file1.txt`])
>>> next(g)
(`E:\Python\script\函式\test\aa\bb1`, [], [`file2.txt`])
>>> next(g)
(`E:\Python\script\函式\test\aa\bb2`, [], [`file3.txt`])
>>> next(g)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

我們在開啟檔案的時候需要找到檔案的絕對路徑,現在可以通過字串拼接的方法把第一部分和第三部分進行拼接
用迴圈開啟:

import os
dir_g = os.walk(r"E:Pythonscript函式	est")
for dir_path in dir_g:
    print(dir_path)

結果:

(`E:\Python\script\函式\test`, [`aa`], [])
(`E:\Python\script\函式\test\aa`, [`bb1`, `bb2`], [`file1.txt`])
(`E:\Python\script\函式\test\aa\bb1`, [], [`file2.txt`])
(`E:\Python\script\函式\test\aa\bb2`, [], [`file3.txt`])

將查詢出來的檔案和路徑進行拼接,拼接成絕對路徑

import os
dir_g = os.walk(r"E:Pythonscript函式	est")
for dir_path in dir_g:
    for file in dir_path[2]:
        file = "%s\%s" %(dir_path[0],file)
        print(file)

執行結果:

E:Pythonscript函式	estaafile1.txt
E:Pythonscript函式	estaab1file2.txt
E:Pythonscript函式	estaab2file3.txt

用函式實現:

import os
def search():
    while True:
        dir_name = yield
        dir_g = os.walk(dir_name)
        for dir_path in dir_g:
            for file in dir_path[2]:
                file = "%s\%s" %(dir_path[0],file)
                print(file)
g = search()
next(g)
g.send(r"E:Pythonscript函式	est")

為了把結果返回給下一流程

@init   # 初始化生成器
def search(target):
    while True:
        dir_name = yield
        dir_g = os.walk(dir_name)
        for pardir,_,files in dir_g:
            for file in files:
                abspath = r"%s\%s" %(pardir,file)
                target.send(abspath)

第二階段:開啟檔案

@init
def opener(target):
    while True:
        abspath=yield
        with open(abspath,`rb`) as f:
            target.send((abspath,f))

第三階段:迴圈讀出每一行內容

@init
def cat(target):
    while True:
        abspath,f=yield #(abspath,f)
        for line in f:
            res=target.send((abspath,line))
            if res:break

第四階段:過濾

@init
def grep(pattern,target):
    tag=False
    while True:
        abspath,line=yield tag
        tag=False
        if pattern in line:
            target.send(abspath)
            tag=True

第五階段:列印該行屬於的檔名

@init
def printer():
    while True:
        abspath=yield
        print(abspath)
g = search(opener(cat(grep(`error`.encode(`utf-8`), printer()))))
g.send(r`E:Pythonscript函式	est`)

執行結果:

E:Pythonscript函式	estaafile1.txt
E:Pythonscript函式	estaab2file3.txt