協程--流暢的Python第16章讀書筆記

1 最簡單的使用演示:

def simple_coroutine():
    print('-> coroutine started')
    x = yield
    print('-> coroutine received:', x)

my_coro = simple_coroutine()
my_coro

Out[64]:

<generator object simple_coroutine at 0x7ffac9308048>

In [65]:

next(my_coro)
-> coroutine started

In [66]:

my_coro.send(42)
-> coroutine received: 42
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-66-7c96f97a77cb> in <module>
----> 1 my_coro.send(42)

StopIteration: 

由於simple_coroutine()是一個生成器,所以在my_coro = simple_coroutine()調用時並不執行,此時的生成器處於未激活狀態

直到調用next(my_coro)時,才運行到yield部分,並且只運行到yield部分,尚未賦值給到x

調用my_coro.send(42)時,相當於調用一次next,同時將yield賦值爲42,則函數打印出received: 42,且由於找不到下一個yield,生成發出異常退出

 

2、生成器狀態:

GEN_CREATED: 創建狀態

GEN_RUNNING: 運行狀態

GEN_SUSPENDED: 在yield表達式處暫停狀態

GEN_CLOSE:關閉狀態

生成器被調用時處於創建狀態,在調用next時尚未執行到yield表達式處爲運行狀態(激活),一旦運行到表達式處,程序掛起,此時處於暫停狀態,當程序找不到下一個yield時,生成器關閉,同時拋出關閉異常StopIteration

1中創建生成器後,直接調用my_coro.send(42)時,會發生如下錯誤

my_coro.send(55)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-63-8cb330c8cec3> in <module>
----> 1 my_coro.send(55)

TypeError: can't send non-None value to a just-started generator
此時生成器還在創建狀態,尚未被激活,不能執行這個操作

 

關於狀態的相關代碼演示:

def simple_coro2(a):
    print('-> started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Recevied: c =', c)
    
my_coro2 = simple_coro2(14)
from inspect import getgeneratorstate
getgeneratorstate(my_coro2)

Out[12]:

'GEN_CREATED'

In [13]:

next(my_coro2)
-> started: a = 14
dd:  14

In [14]:

getgeneratorstate(my_coro2)

Out[14]:

'GEN_SUSPENDED'

In [15]:

my_coro2.send(28)
-> Received: b = 28
dd:  42

In [10]:

my_coro2.send(99)
-> Recevied: c = 99
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-10-977093b924ab> in <module>
----> 1 my_coro2.send(99)

StopIteration: 

In [11]:

getgeneratorstate(my_coro2)

Out[11]:

'GEN_CLOSED'
 

從這個演示可以看到,函數被調用後處於創建狀態,調用next後,達到暫停狀態,至於運行狀態,一旦next調用結束,運行狀態也就結束了。

3 使用協程計算移動平均值

1)計算移動平均值

image

調用next,運行到yield右側,由於第一次average是None,因此傳遞出來的是None,調用send時即爲每次的計算出來的平均值

每次調用send時,都是運行到yield右側,通過average將平均值傳遞出來

 

2 預激協程的裝飾器

image

通過裝飾器完成協程的激活過程,防止忘記調用激活函數next引發問題。

 

3 終止協程和異常處理

協程終止時會拋出StopIteration異常, 我們在程序中可以通過捕獲此異常進行處理:

image

 

4 使用yield from

image

可改寫成:

image

 

使用yield from 鏈接可迭代的對象

image

 

使用yield from 計算平均值,並輸出統計報告

## 使用yield from 計算平均值,並輸出統計報告
from collections import namedtuple
Result = namedtuple('Result', 'count average')

# 子生成器,計算出平均值
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # 具體的體重,身高值綁定到變量term上
        if term is None:  # 爲空時,跳出循環,生成器終止
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)  # 返回的Result會成爲grouper函數中yield from 表達式的值

# 委派生成器
def grouper(results, key):
    while True:  # 這個循環每次迭代時會新建一個averager實例,每個實例都是作爲協程使用的生成器對象
        results[key] = yield from averager() # grouper發送的每個值都會經由yield from處理,通過管道傳給averager實例
                                              # grouper會在yield from表達式處暫停,等待averager實例處理客戶端發來的值。
                                              # averager實例運行完畢後,返回的值綁定到results[key]上。while循環會不斷
                                              # 創建averager實例,處理更多的值
    
# 輸出報告
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))
        
data = {
    'girls;kg':[40.9, 38.5, 44.3, 42.2],
    'girls;m':[1.6, 1.51, 1.4, 1.3],
    'boys;kg':[39.0, 40.8, 43.2, 40.8],
    'boys;m':[1.38, 1.5, 1.32, 1.25],
}

def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # group是調用grouper函數得到的生成器對象,傳給grouper函數的第一個參數是results,
                                       # 用於收集結果,第二個參數作爲鍵。group作爲協程使用。
        next(group)                    # 預激group協程
        for value in values:
            group.send(value)          # 把各個value傳給grouper.傳入的值最終到達averager函數中的term = yield那一行。
                                       # grouper永遠不知道傳入值是什麼。
        group.send(None)              # 把None傳入grouper, 導致當前的averager實例終止,也讓grouper繼續執行,再創建一個averager實例,
                                      # 處理下一組值

    report(results)
    
    
main(data)

image

 

5 離散事件仿真

離散事件仿真是一種把系統建模成一系列事件的仿真類型。在離散事件仿真中,仿真“鍾”向前推進的量不是固定的,而是直接推進到下一個事件模型的模擬時間。假如我們抽象模擬出租車的運營過程,其中一個事件是乘客上車,下一個事件則是乘客下車。不管乘客坐了5分鐘還是50分鐘,一旦乘客下車,仿真鍾就會更新,指向此次運營的結束時間。使用離散事件仿真可以在不到一秒鐘的時間內模擬一年的出租車運營過程。

仿真程序taxi_sim.py會創建幾輛出租車,每輛車會拉幾個乘客,然後回家。出租車首先駛離車庫,四處徘徊,尋找乘客;拉到乘客後,行程開始;乘客下車後,繼續四處徘徊。

四處徘徊和行程所用的時間使用指數分佈生在。爲了讓顯示的信息更加整潔,時間使用取整的分鐘數。每輛出租車每次的狀態變化都是一個事件。

本程序將離散事件(併發)整理到一個序列中執行。

出租車每隔5分鐘從車庫中出發

0號出租車2分鐘後拉到乘客(time=2),1號出租車3分鐘後拉到乘客(time=8),2號出租車5分鐘後拉到乘客(time=15)

0號出租車拉了兩個乘客:第一個乘客從time=2時上車,到time=18時下車,第二個乘客從time=28時上車,到time=65時下車-----這是此次仿真中最長的行程

1號出租車拉了四個乘客,在time=110時回家

2號出租車拉了六個乘客,在time=109時回家。這輛車最後一次行程從time=97時開始,只持續了一分鐘。

1號出租車的第一次 行程從time=8時開始,在這個過程中2號出租車離開了車庫(time=10),而且完成了兩次行程。

在此次運行示例中,所有排定的事件都在默認的仿真時間內(180分鐘)完成。最後一次事件發生在time=110時。

在Event實例中,time字段是事件發生時的仿真時間,proc字段是出租車進程實例的編號,action字段是描述活動的字符串。

附源代碼:taxi_sim.py

import random
import collections
import queue
import argparse

DEFAULT_NUMBER_OF_TAXIS = 3
DEFAULT_END_TIME = 180
SEARCH_DURATION = 5
TRIP_DURATION = 20
DEPARTURE_INTERVAL = 5

Event = collections.namedtuple('Event', 'time proc action')


# 出租車進程。
def taxi_process(ident, trips, start_time=0):
    """每次狀態變化時向仿真程序產出一個事件"""
    time = yield Event(start_time, ident, 'leave garage')
    for i in range(trips):
        time = yield Event(time, ident, 'pick up passenger')
        time = yield Event(time, ident, 'drop off passenger')
    yield Event(time, ident, 'going home')
    # 結束出租車進程


# 出租車仿真程序主程序。
class Simulator:

    def __init__(self, procs_map):
        self.events = queue.PriorityQueue()  # 優先級隊列,put方法放入數據,一般是一個數組(3, someting),get()方法數值小的優先出隊
        self.procs = dict(procs_map)  # 創建字典的副本

    def run(self, end_time):
        """調度並顯示事件,直到事件結束"""
        # 調度各輛出租車的第一個事件
        for _, proc in sorted(self.procs.items()):
            first_event = next(proc)  # 第一個事件是所有車離開車庫,也是爲了激活子生成器
            self.events.put(first_event)  # 所有車的第一個事件放到優先隊列中,time小的優先出來

        # 此次仿真的主循環
        sim_time = 0
        while sim_time < end_time:
            if self.events.empty():
                print('***事件結束***')
                break
            current_event = self.events.get()  # 取出time最小的事件
            sim_time, proc_id, previous_action = current_event  # 元組解包
            print('taxi:', proc_id, proc_id * '  ', current_event)
            active_proc = self.procs[proc_id]  # 取出當前事件對象。是一個子生成器對象,下面要對這個對象send(time)來獲得下一個yield的返回值
            next_time = sim_time + comput_duration(previous_action)  # 隨機計算下一個時間
            try:
                next_event = active_proc.send(next_time)  # 下一個事件是子生成器執行到下一個yield的返回值
            except StopIteration:  # StopIteration異常說明當前子生成器執行完畢,從字典中刪除它
                del self.procs[proc_id]
            else:
                self.events.put(next_event)  # 否則就把下一個事件放入優先隊列中
        else:  # 如果while循環沒有以break結束,那麼輸出結束信息
            msg = '*** 仿真結束。{}個車沒有回家 ***'
            print(msg.format(self.events.qsize()))
        # 仿真結束


def comput_duration(previous_action):
    """使用指數分佈計算操作的耗時"""
    if previous_action in ['leave garage', 'drop off passenger']:
        interval = SEARCH_DURATION
    elif previous_action == 'pick up passenger':
        # 新狀態是行程開始
        interval = TRIP_DURATION
    elif previous_action == 'going home':
        interval = 1
    else:
        raise ValueError('未知的活動:{}'.format(previous_action))
    return int(random.expovariate(1/interval) + 1)


def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, seed=None):
    # 構建隨機生成器,構建過程,運行仿真程序
    if seed is not None:
        random.seed(seed)  # 指定seed的值時,用這個seed可以使隨機數隨機出來的相等
    taxis = {i: taxi_process(i, (i+1*2), i*DEPARTURE_INTERVAL) for i in range(num_taxis)}  # 字典生成式,生成指定數量的子生成器對象
    sim = Simulator(taxis)  # 實例化仿真主循環
    sim.run(end_time)  # run it!


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='出租車運行仿真')  # 創建參數解析對象,添加描述
    parser.add_argument('-e', '--end_time', type=int, default=DEFAULT_END_TIME)  # 添加-e參數,默認值爲180
    parser.add_argument('-t', '--taxis', type=int, default=DEFAULT_NUMBER_OF_TAXIS, help='出租車出行數量, default=%s' %DEFAULT_NUMBER_OF_TAXIS)  # 添加-t參數,用來指定出租車數量,默認值爲3
    parser.add_argument('-s', '--seed', type=int, default=3, help='隨機生成seed')  # 添加-s參數,用來設置seed值,如果seed值一樣那麼隨機出來的結果也會一樣,默認值爲None
    args = parser.parse_args()  # 這個函數用來獲取參數
    main(args.end_time, args.taxis, args.seed)  # 通過上面函數的屬性的到輸入的參數,屬性可以是雙橫線後的字符串也可以是添加參數函數的第一個不加橫線的字符串

 

輸出結果:

taxi: 0  Event(time=0, proc=0, action='leave garage')
taxi: 0  Event(time=2, proc=0, action='pick up passenger')
taxi: 1    Event(time=5, proc=1, action='leave garage')
taxi: 1    Event(time=8, proc=1, action='pick up passenger')
taxi: 2      Event(time=10, proc=2, action='leave garage')
taxi: 2      Event(time=15, proc=2, action='pick up passenger')
taxi: 2      Event(time=17, proc=2, action='drop off passenger')
taxi: 0  Event(time=18, proc=0, action='drop off passenger')
taxi: 2      Event(time=18, proc=2, action='pick up passenger')
taxi: 2      Event(time=25, proc=2, action='drop off passenger')
taxi: 1    Event(time=27, proc=1, action='drop off passenger')
taxi: 2      Event(time=27, proc=2, action='pick up passenger')
taxi: 0  Event(time=28, proc=0, action='pick up passenger')
taxi: 2      Event(time=40, proc=2, action='drop off passenger')
taxi: 2      Event(time=44, proc=2, action='pick up passenger')
taxi: 1    Event(time=55, proc=1, action='pick up passenger')
taxi: 1    Event(time=59, proc=1, action='drop off passenger')
taxi: 0  Event(time=65, proc=0, action='drop off passenger')
taxi: 1    Event(time=65, proc=1, action='pick up passenger')
taxi: 2      Event(time=65, proc=2, action='drop off passenger')
taxi: 2      Event(time=72, proc=2, action='going home')
taxi: 0  Event(time=76, proc=0, action='going home')
taxi: 1    Event(time=80, proc=1, action='drop off passenger')
taxi: 1    Event(time=88, proc=1, action='going home')
***事件結束***
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章