摘要:一個典型的上下文管理器類如下處理異常正如方法名明確告訴我們的,方法負責進入上下的準備工作,如果有需要可以返回一個值,這個值將會被賦值給中的。總結都是關于上下文管理器的內容,與協程關系不大。
Part 1 傳送門
David Beazley 的博客
PPT 下載地址
在 Part 1 我們已經介紹了生成器的定義和生成器的操作,現在讓我們開始使用生成器。Part 2 主要描述了如何使用 yield 和 contextmanager 創建一個上下文管理器,并解釋了原理。
理解上下文可以聯想我們做閱讀理解時要解讀文章某處的意思需要閱讀該處前后段落,正是前后文提供了理解的“背景”。而程序的運行的上下文也可以理解為程序在運行時的某些變量,正是這些變量構成了運行環境,讓程序可以完成操作。
Python 中的上下文管理器提供這樣一種功能,為你的程序運行時提供一個特定“空間”,當進入這個空間時 Python 上下文管理器 為你做一些準備工作。這個“空間”中一般含有特殊的變量,你在這個“空間”中進行一些操作,然后離開。在你離開時 Python 上下文管理器又會幫你做一些收尾工作,保證不會污染運行環境。
下面是一些常見的代碼模式
# 讀取文件 f = open() # do something f.close() # 使用鎖 lock.acquire() # do somethin lock.release() # 進行數據庫操作 db.start_transaction() # do something db.commit() # 對某段代碼進行計時 start = time.time() # do something end = time.time()
這些代碼進行的都是“先做這個(準備工作,比如獲取一個數據庫連接),然后做這個(比如寫入數據),最后整理工作環境(如提交改動,關閉鏈接,釋放資源等)。
如果使用 with 可以這樣寫:
witn open(filename) as f: # do something pass with lock(): # do something pass
with 語句實際上使用了實現了 __enter__ 和 __exit__ 方法的上下文管理器類。一個典型的上下文管理器類如下:
clss ContextManager: def __enter__(self): return value def __exit__(self, exc_type, val, tb): if exec_type is None: return else: # 處理異常 return True if handled else False
正如方法名明確告訴我們的,__enter__ 方法負責進入上下的準備工作,如果有需要可以返回一個值,這個值將會被賦值給 with ContextManager() as ret_value 中的 ret_value 。__exit__ 則負責收尾工作,這包括了異常處理。
對于這樣一段代碼
with ContextManager() as var: # do something
相當于
ctxmanager = ContextManager() var = ctxmanager.__enter__() # do somethin ctxmanager.__exit__()
一個可用的例子:
import tempfile import shutil class TmpDir: def __enter__(self): self.dirname = tempfile.mkdtemp() return self.dirname def __exit__(self, exc, val, tb): shutil.rmtree(self.dirname)
這個上下文管理提供臨時文件的功能,在 with 語句結束后會自動刪除臨時文件夾。
with TempDir() as dirname: # 使用臨時文件夾進行一些操作 pass
關于上面兩個特殊方法的文檔可以在 Python 文檔的 Context Manager Types 找到。另外關于 with 關鍵字的詳細說明參考 PEP 343,不過這篇 PEP 不是很好讀,Good Luck :simple_smile:!
使用 yield 和 contextmanager能看到這里的都應該對上下文管理器有所了解,準備好把 yield 加入我們的上下文管理器代碼中。
先看一個例子
import tempfile, shutil from contextlib import contextmanager @contextmanager def tempdir(): outdir = tempfile.mkdtemp() try: yield outdir finally: shutil.rmtree(outdir)
與使用上下文管理器類的實現方式不同,這里我們沒有顯式實現 __enter__ 和 __exit__,而是通過 contextmanager 裝飾器和 yield 實現,你可以試試這兩種方式是等價的。
要理解上面的代碼,可以把 yield 想象為一把剪刀,把這個函數一分為二,上部分相當于 __enter__,下部分相當于 __exit__。我這樣說大家應該明白了吧。
import tempfile, shutil from contextlib import contextmanager @contextmanager def tempdir(): outdir = tempfile.mkdtemp() # try: # __enter__ yield outdir # --cut---╳----------------------------------- finally: # shutil.rmtree(outdir) # __exit__
實現“剪刀”功能關鍵在于 contextmanager 。對于上面的代碼,我們來一步一步地結構它:
contextmanager 裝飾器contextmanager 其實使用了一個上下文管理器類,這個類在在初始化時需要提供一個生成器。
class GeneratorCM: def __init__(self, gen): self.gen = gen def __enter__(self): ... def __exit__(self, exc, val, tb): ...
contextmanager 的實現如下
def contextmanager(func): def run(*args, **kwargs): return GeneratorCM(func(*args, **kwargs)) return run
由于 contextmanger 所裝飾的函數里有 yield 所以我們在調用 func(*args, **kwargs) 時返回的是一個生成器。要使這個生成器前進,我們需要調用 next 函數
讓生成器前進def __enter__(self): return next(self.gen)
GeneratorCM 的 __ente__ 方法會讓生成器前進到 yield 語句處,并返回產出值。
收尾def __exit__(self, exc, val, tb): try: if exc is None: next(self.gen) else: self.gen.throw(exc, val, tb) raise RuntimeError("Generator didn"t stop") except StopIteration: return True except: if sys.exc_info()[1] is not val: raise
__exit__ 函數的邏輯比較復雜,如果沒有傳入異常,首先它會嘗試對生成器調用 next,正常情況下這會拋出 StopIteration ,這個異常會被不做并返回 True ,告訴解釋器正常退出;如果傳入異常,會使用 throw 在 yield 處拋出這個異常;如果有其他未捕捉的錯誤,就重新拋出該錯誤。
實際的代碼實現會更加復雜,還有一些異常情況沒有處理
沒有相關值的異常
在 with 語句塊中拋出的 StopIteration
在上下文管理器中拋出的異常
如果你對怎么實現感興趣,你可以閱讀代碼或者再一次閱讀 PEP 343。
總結Part 2 都是關于上下文管理器的內容,與協程關系不大。但通過這部分我們可以看到 yield 完全不同的用法,也熟悉了控制流 (control-flow) ,這與 Part 3 的異步處理流程有很大關系。讓我們 Part 3 再見。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/41947.html
摘要:生成器用于定義生成器函數只要存在該函數必定是一個生成器調用該函數返回一個生成器讓一個生成器前進使用使一個生成器前進到下一個語句處,并將產出值作為其返回值。 前言 這篇文章大部分來自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。這個PPT很長而且非常燒腦,建議在閱讀前應了解 Python 的生成器與攜...
摘要:源碼之分析的協程原理分析版本為支持異步,實現了一個協程庫。提供了回調函數注冊當異步事件完成后,調用注冊的回調中間結果保存結束結果返回等功能注冊回調函數,當被解決時,改回調函數被調用。相當于喚醒已經處于狀態的父協程,通過回調函數,再執行。 tornado 源碼之 coroutine 分析 tornado 的協程原理分析 版本:4.3.0 為支持異步,tornado 實現了一個協程庫。 ...
摘要:項目地址我之前翻譯了協程原理這篇文章之后嘗試用了模式下的協程進行異步開發,確實感受到協程所帶來的好處至少是語法上的。 項目地址:https://git.io/pytips 我之前翻譯了Python 3.5 協程原理這篇文章之后嘗試用了 Tornado + Motor 模式下的協程進行異步開發,確實感受到協程所帶來的好處(至少是語法上的:D)。至于協程的 async/await 語法是如...
摘要:協程的判斷條件下面我們來著重看下的源碼,因為從這里開始就涉及到協程的判斷。第二點是關鍵點,用來判斷該方法的調用是否使用到了協程。原理我們先來看下使用協程是怎么寫的這是一個標準的協程寫法,然后我們再套用上面的條件,發現完全匹配不到。 第一眼看,跟我之前印象中的有點區別(也不知道是什么版本),return的時候居然...
閱讀 2395·2021-11-11 16:54
閱讀 1204·2021-09-22 15:23
閱讀 3644·2021-09-07 09:59
閱讀 1990·2021-09-02 15:41
閱讀 3283·2021-08-17 10:13
閱讀 3037·2019-08-30 15:53
閱讀 1235·2019-08-30 13:57
閱讀 1210·2019-08-29 15:16