摘要:一個(gè)典型的上下文管理器類如下處理異常正如方法名明確告訴我們的,方法負(fù)責(zé)進(jìn)入上下的準(zhǔn)備工作,如果有需要可以返回一個(gè)值,這個(gè)值將會(huì)被賦值給中的??偨Y(jié)都是關(guān)于上下文管理器的內(nèi)容,與協(xié)程關(guān)系不大。
Part 1 傳送門
David Beazley 的博客
PPT 下載地址
在 Part 1 我們已經(jīng)介紹了生成器的定義和生成器的操作,現(xiàn)在讓我們開始使用生成器。Part 2 主要描述了如何使用 yield 和 contextmanager 創(chuàng)建一個(gè)上下文管理器,并解釋了原理。
理解上下文可以聯(lián)想我們做閱讀理解時(shí)要解讀文章某處的意思需要閱讀該處前后段落,正是前后文提供了理解的“背景”。而程序的運(yùn)行的上下文也可以理解為程序在運(yùn)行時(shí)的某些變量,正是這些變量構(gòu)成了運(yùn)行環(huán)境,讓程序可以完成操作。
Python 中的上下文管理器提供這樣一種功能,為你的程序運(yùn)行時(shí)提供一個(gè)特定“空間”,當(dāng)進(jìn)入這個(gè)空間時(shí) Python 上下文管理器 為你做一些準(zhǔn)備工作。這個(gè)“空間”中一般含有特殊的變量,你在這個(gè)“空間”中進(jìn)行一些操作,然后離開。在你離開時(shí) Python 上下文管理器又會(huì)幫你做一些收尾工作,保證不會(huì)污染運(yùn)行環(huán)境。
下面是一些常見(jiàn)的代碼模式
# 讀取文件 f = open() # do something f.close() # 使用鎖 lock.acquire() # do somethin lock.release() # 進(jìn)行數(shù)據(jù)庫(kù)操作 db.start_transaction() # do something db.commit() # 對(duì)某段代碼進(jìn)行計(jì)時(shí) start = time.time() # do something end = time.time()
這些代碼進(jìn)行的都是“先做這個(gè)(準(zhǔn)備工作,比如獲取一個(gè)數(shù)據(jù)庫(kù)連接),然后做這個(gè)(比如寫入數(shù)據(jù)),最后整理工作環(huán)境(如提交改動(dòng),關(guān)閉鏈接,釋放資源等)。
如果使用 with 可以這樣寫:
witn open(filename) as f: # do something pass with lock(): # do something pass
with 語(yǔ)句實(shí)際上使用了實(shí)現(xiàn)了 __enter__ 和 __exit__ 方法的上下文管理器類。一個(gè)典型的上下文管理器類如下:
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__ 方法負(fù)責(zé)進(jìn)入上下的準(zhǔn)備工作,如果有需要可以返回一個(gè)值,這個(gè)值將會(huì)被賦值給 with ContextManager() as ret_value 中的 ret_value 。__exit__ 則負(fù)責(zé)收尾工作,這包括了異常處理。
對(duì)于這樣一段代碼
with ContextManager() as var: # do something
相當(dāng)于
ctxmanager = ContextManager() var = ctxmanager.__enter__() # do somethin ctxmanager.__exit__()
一個(gè)可用的例子:
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)
這個(gè)上下文管理提供臨時(shí)文件的功能,在 with 語(yǔ)句結(jié)束后會(huì)自動(dòng)刪除臨時(shí)文件夾。
with TempDir() as dirname: # 使用臨時(shí)文件夾進(jìn)行一些操作 pass
關(guān)于上面兩個(gè)特殊方法的文檔可以在 Python 文檔的 Context Manager Types 找到。另外關(guān)于 with 關(guān)鍵字的詳細(xì)說(shuō)明參考 PEP 343,不過(guò)這篇 PEP 不是很好讀,Good Luck :simple_smile:!
使用 yield 和 contextmanager能看到這里的都應(yīng)該對(duì)上下文管理器有所了解,準(zhǔn)備好把 yield 加入我們的上下文管理器代碼中。
先看一個(gè)例子
import tempfile, shutil from contextlib import contextmanager @contextmanager def tempdir(): outdir = tempfile.mkdtemp() try: yield outdir finally: shutil.rmtree(outdir)
與使用上下文管理器類的實(shí)現(xiàn)方式不同,這里我們沒(méi)有顯式實(shí)現(xiàn) __enter__ 和 __exit__,而是通過(guò) contextmanager 裝飾器和 yield 實(shí)現(xiàn),你可以試試這兩種方式是等價(jià)的。
要理解上面的代碼,可以把 yield 想象為一把剪刀,把這個(gè)函數(shù)一分為二,上部分相當(dāng)于 __enter__,下部分相當(dāng)于 __exit__。我這樣說(shuō)大家應(yīng)該明白了吧。
import tempfile, shutil from contextlib import contextmanager @contextmanager def tempdir(): outdir = tempfile.mkdtemp() # try: # __enter__ yield outdir # --cut---╳----------------------------------- finally: # shutil.rmtree(outdir) # __exit__
實(shí)現(xiàn)“剪刀”功能關(guān)鍵在于 contextmanager 。對(duì)于上面的代碼,我們來(lái)一步一步地結(jié)構(gòu)它:
contextmanager 裝飾器contextmanager 其實(shí)使用了一個(gè)上下文管理器類,這個(gè)類在在初始化時(shí)需要提供一個(gè)生成器。
class GeneratorCM: def __init__(self, gen): self.gen = gen def __enter__(self): ... def __exit__(self, exc, val, tb): ...
contextmanager 的實(shí)現(xiàn)如下
def contextmanager(func): def run(*args, **kwargs): return GeneratorCM(func(*args, **kwargs)) return run
由于 contextmanger 所裝飾的函數(shù)里有 yield 所以我們?cè)谡{(diào)用 func(*args, **kwargs) 時(shí)返回的是一個(gè)生成器。要使這個(gè)生成器前進(jìn),我們需要調(diào)用 next 函數(shù)
讓生成器前進(jìn)def __enter__(self): return next(self.gen)
GeneratorCM 的 __ente__ 方法會(huì)讓生成器前進(jìn)到 yield 語(yǔ)句處,并返回產(chǎn)出值。
收尾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__ 函數(shù)的邏輯比較復(fù)雜,如果沒(méi)有傳入異常,首先它會(huì)嘗試對(duì)生成器調(diào)用 next,正常情況下這會(huì)拋出 StopIteration ,這個(gè)異常會(huì)被不做并返回 True ,告訴解釋器正常退出;如果傳入異常,會(huì)使用 throw 在 yield 處拋出這個(gè)異常;如果有其他未捕捉的錯(cuò)誤,就重新拋出該錯(cuò)誤。
實(shí)際的代碼實(shí)現(xiàn)會(huì)更加復(fù)雜,還有一些異常情況沒(méi)有處理
沒(méi)有相關(guān)值的異常
在 with 語(yǔ)句塊中拋出的 StopIteration
在上下文管理器中拋出的異常
如果你對(duì)怎么實(shí)現(xiàn)感興趣,你可以閱讀代碼或者再一次閱讀 PEP 343。
總結(jié)Part 2 都是關(guān)于上下文管理器的內(nèi)容,與協(xié)程關(guān)系不大。但通過(guò)這部分我們可以看到 yield 完全不同的用法,也熟悉了控制流 (control-flow) ,這與 Part 3 的異步處理流程有很大關(guān)系。讓我們 Part 3 再見(jiàn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/41947.html
摘要:生成器用于定義生成器函數(shù)只要存在該函數(shù)必定是一個(gè)生成器調(diào)用該函數(shù)返回一個(gè)生成器讓一個(gè)生成器前進(jìn)使用使一個(gè)生成器前進(jìn)到下一個(gè)語(yǔ)句處,并將產(chǎn)出值作為其返回值。 前言 這篇文章大部分來(lái)自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。這個(gè)PPT很長(zhǎng)而且非常燒腦,建議在閱讀前應(yīng)了解 Python 的生成器與攜...
摘要:本文先回顧生成器,然后過(guò)渡到協(xié)程編程。其作用主要體現(xiàn)在三個(gè)方面數(shù)據(jù)生成生產(chǎn)者,通過(guò)返回?cái)?shù)據(jù)數(shù)據(jù)消費(fèi)消費(fèi)者,消費(fèi)傳來(lái)的數(shù)據(jù)實(shí)現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點(diǎn)應(yīng)當(dāng)關(guān)注控制權(quán)轉(zhuǎn)讓的時(shí)機(jī),以及協(xié)程的運(yùn)作方式。 轉(zhuǎn)載請(qǐng)注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請(qǐng)求 cookie web響應(yīng) sess...
摘要:源碼之分析的協(xié)程原理分析版本為支持異步,實(shí)現(xiàn)了一個(gè)協(xié)程庫(kù)。提供了回調(diào)函數(shù)注冊(cè)當(dāng)異步事件完成后,調(diào)用注冊(cè)的回調(diào)中間結(jié)果保存結(jié)束結(jié)果返回等功能注冊(cè)回調(diào)函數(shù),當(dāng)被解決時(shí),改回調(diào)函數(shù)被調(diào)用。相當(dāng)于喚醒已經(jīng)處于狀態(tài)的父協(xié)程,通過(guò)回調(diào)函數(shù),再執(zhí)行。 tornado 源碼之 coroutine 分析 tornado 的協(xié)程原理分析 版本:4.3.0 為支持異步,tornado 實(shí)現(xiàn)了一個(gè)協(xié)程庫(kù)。 ...
摘要:項(xiàng)目地址我之前翻譯了協(xié)程原理這篇文章之后嘗試用了模式下的協(xié)程進(jìn)行異步開發(fā),確實(shí)感受到協(xié)程所帶來(lái)的好處至少是語(yǔ)法上的。 項(xiàng)目地址:https://git.io/pytips 我之前翻譯了Python 3.5 協(xié)程原理這篇文章之后嘗試用了 Tornado + Motor 模式下的協(xié)程進(jìn)行異步開發(fā),確實(shí)感受到協(xié)程所帶來(lái)的好處(至少是語(yǔ)法上的:D)。至于協(xié)程的 async/await 語(yǔ)法是如...
摘要:協(xié)程的判斷條件下面我們來(lái)著重看下的源碼,因?yàn)閺倪@里開始就涉及到協(xié)程的判斷。第二點(diǎn)是關(guān)鍵點(diǎn),用來(lái)判斷該方法的調(diào)用是否使用到了協(xié)程。原理我們先來(lái)看下使用協(xié)程是怎么寫的這是一個(gè)標(biāo)準(zhǔn)的協(xié)程寫法,然后我們?cè)偬子蒙厦娴臈l件,發(fā)現(xiàn)完全匹配不到。 第一眼看,跟我之前印象中的有點(diǎn)區(qū)別(也不知道是什么版本),return的時(shí)候居然...
閱讀 2569·2021-11-11 16:54
閱讀 1288·2021-09-22 15:23
閱讀 3748·2021-09-07 09:59
閱讀 2141·2021-09-02 15:41
閱讀 3360·2021-08-17 10:13
閱讀 3154·2019-08-30 15:53
閱讀 1302·2019-08-30 13:57
閱讀 1285·2019-08-29 15:16