回覆列表
  • 1 # 使用者3551946793047

    生成器如何進化成協程

    從Python2.5以後, yield關鍵字可以在表示式中使用,而且生成器API中增加了.send(valie)方法。生成器的呼叫方可以使用.send()方法傳送資料,傳送的資料會成為生成器函式中yield表示式的值。因此,生成器可以作為協程使用。

    協程是指一個過程,這個過程與呼叫方寫作,產出由呼叫方提供的值。

    用作協程的生成器的基本行為

    請看以下示例:

    協程包含以下4種狀態:

    "GEN_CREATED":等待開始執行。 "GEN_RUNNING":直譯器正在執行。 "GEN_SUSPENDED":在yield表示式處暫停。 "GEN_CLOSED":執行結束。

    當前狀態可以使用inspect.getgeneratorstate函式確定,該函式會返回以上狀態字串中的一個。

    因為send方法的引數會成為暫停的yield表示式的值,所以,僅當協程處於暫停狀態時才能呼叫send方法,例如a.send(2)。不過,如果協程還沒啟用(即:狀態是"GEN_CREATED"),情況就不同了。因此始終要呼叫next(a)啟用協程,也可以呼叫a.send(None),效果一樣。

    示例:使用協程計算移動平均值

    上述示例中,呼叫next(a)函式後,協程會向前執行到yield表示式,產出average變數的初始值None,因此不會出現在控制檯中。此時,協程在yield表示式處暫停,等到呼叫方傳送值。a.send(10)那一行傳送一個值,啟用協程,把傳送的值賦給term,並更新total、count和average三個變數的值,然後開始while迴圈的下一次迭代,產出average變數的值,等待下一次為term變數賦值。

    預激協程的裝飾器

    如果不預激,那麼協程沒什麼用。呼叫a.send10(之前),一定要呼叫next(a)。為了簡化協程的用法,有時會使用一個預激裝飾器。示例如下:

    很多框架都提供了處理協程的特殊裝飾器,不過不是所有裝飾器都用於預激協程,有些會提供其他服務,例如勾入事件迴圈。

    使用yield from句法呼叫協程時,會自動預激,因此與上述示例中的@coroutine裝飾器不相容。Python3.4標準庫裡的asyncio.coroutine裝飾器不會預激協程,因此能相容yield from語法。

    終止協程和異常處理

    協程中未處理的異常會向上冒泡,傳遞給next函式或send方法的呼叫方(即觸發協程的物件)。

    由於協程內沒有處理異常,導致協程終止。如果試圖重新啟用協程,會丟擲StopIteration異常。

    從Python2.5開始,客戶程式碼可以在生成器物件上呼叫兩個方法,顯式地把異常發給協程。這兩個方法是throw和close。

    generator.throw(exc_type, exc_value, traceback) 致使生成器在暫停的yield表示式處丟擲指定的異常。如果生成器處理了丟擲的異常,程式碼會向前執行到下一個yield表示式,而產出的值會成為呼叫generator.throw方法得到的返回值。如果生成器沒有處理丟擲的異常,異常會向上冒泡,傳到呼叫方的上下文中。generator.close 致使生成器在暫停的yield表打死處丟擲GeneratorExit異常。如果生成器沒有處理這個異常,或者丟擲了StopIteration異常(通常是指執行到結尾),呼叫方不會報錯。如果收到GeneratorExit異常,生成器一定不能產出值,否則直譯器會丟擲RuntimeError異常。生成器丟擲的其他異常會向上冒泡,傳給呼叫方。

    請看以下示例:

    啟用和關閉demo_handle,沒有異常。

    把DemoException傳入demo_handle不會導致協程終止。

    如果無法處理傳入的異常,協程會終止。

    使用yield from

    yield from是全新的語言結構。它的作用比yield多很多,因此人們認為繼續使用yield關鍵字多少會引起誤解。在其他語言中,類似的結構使用await關鍵字,這個名稱好多了,因為它傳達了至關重要的一點:在生成器gen中使用yield from subgen()時,subgen會獲得控制權,把產出的值傳給gen的呼叫方,即呼叫方可以直接控制subgen。與此同時,gen會阻塞,等待subgen終止。

    yield from可用於簡化for迴圈中的yield表示式。例如:

    可以改寫為:

    yield from x表示式對x物件所做的第一件事是,呼叫iter(x),從中獲取迭代器。因此,x可以是任何可迭代物件。 yield from的主要功能是開啟雙向通道,把最外層的呼叫方與最內層的子生成器連線起來,這樣二者可以直接傳送和產出值,還可以直接傳入異常,而不用在位於中間協程中新增大量處理異常的樣板程式碼。有了這個結構,協程可以透過以前不可能的方式委託職責。 為了說明需要改動的部分,PEP 380使用了一些專門的術語:

    委派生成器:包含yield from 表示式的生成器函式。 子生成器:從yield from 表示式中部分獲取的生成器。 呼叫方:PEP 380使用"呼叫方"這個術語指代呼叫委派生成器的客戶端程式碼。在不同的語境中,使用"客戶端"代替"呼叫方",以此與委派生成器區分開。

    yield from的意義

    子生成器產出的值都直接傳給委派生成器的呼叫方(即客戶端程式碼)。使用send()方法發給委派生成器的值都傳給了子生成器。如果傳送的值是None,那麼會呼叫子生成器的__next__()方法。如果傳送的值不是None,那麼會呼叫子生成器的send()方法。如果呼叫的方法丟擲StopIteration異常,那麼委派生成器恢復執行。任何其他異常都會向上冒泡,傳給委派生成器。生辰器退出時,生成器(或子生成器)中的return expr表示式會觸發StopIteration(expr)異常。yield from表示式的值是子生成器終止時傳給StopIteration異常的第一個引數。

    yield from結構的另外兩個特性與異常和終止有關。

    傳入委派生成器的異常,除了GeneratorExit之外都傳給了子生成器的throw()方法。如果呼叫throw()方法時丟擲了StopIteration異常,委派生成器恢復執行。StopIteration之外的異常會向上冒泡,傳給委派生成器。如果把GeneratorExit異常傳入委派生成器,或者在委派生成器上呼叫close()方法,那麼在子生成器上呼叫close()方法時,如果它有的話。如果呼叫close()方法導致異常丟擲,那麼異常會向上冒泡,傳給委派生成器:否則,委派生成器丟擲GeneratorExit異常。

  • 中秋節和大豐收的關聯?
  • 2021民航招飛行員的條件?