Python 中 with 用法详解
?
淺談 Python 的 with 語句:https://developer.ibm.com/zh/articles/os-cn-pythonwith/
python3,淺談with的神奇魔法:https://blog.csdn.net/lxy210781/article/details/81176687
Python 的 with 語句詳解:https://www.jb51.net/article/51045.htm
深入理解 Python 的 With-as 語句:https://cloud.tencent.com/developer/article/1083148
python with statement 進(jìn)階理解:https://www.iteye.com/blog/jianpx-505469
Python 中的with關(guān)鍵字使用詳解:https://www.jb51.net/article/92387.htm
?
?
由來
?
with…as 是 python 的控制流語句,像 if ,while一樣。with…as 語句是簡化版的 try except finally語句。
先理解一下 try…except…finally 語句是干啥的。實(shí)際上 try…except 語句和 try…finally 語句是兩種語句,用于不同的場景。但是當(dāng)二者結(jié)合在一起時,可以“實(shí)現(xiàn)穩(wěn)定性和靈活性更好的設(shè)計(jì)”。
?
1. try…except 語句
用于處理程序執(zhí)行過程中的異常情況,比如語法錯誤、從未定義變量上取值等等,也就是一些python程序本身引發(fā)的異常、報(bào)錯。比如你在python下面輸入 1 / 0:
>>> 1/0 Traceback (most recent call last):File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero系統(tǒng)會給你一個 ZeroDivisionError 的報(bào)錯。說白了就是為了防止一些報(bào)錯影響你的程序繼續(xù)運(yùn)行,就用try語句把它們抓出來(捕獲)。
try…except 的標(biāo)準(zhǔn)格式:
try: ## normal block except A: ## exc A block except: ## exc other block else: ## noError block程序執(zhí)行流程是:
–>執(zhí)行normal block –>發(fā)現(xiàn)有A錯誤,執(zhí)行 exc A block(即處理異常) –>結(jié)束如果沒有A錯誤呢? –>執(zhí)行normal block –>發(fā)現(xiàn)B錯誤,開始尋找匹配B的異常處理方法,發(fā)現(xiàn)A,跳過,發(fā)現(xiàn)except others(即except:),執(zhí)行exc other block –>結(jié)束如果沒有錯誤呢? –>執(zhí)行normal block –>全程沒有錯誤,跳入else 執(zhí)行noError block –>結(jié)束Tips: 我們發(fā)現(xiàn),一旦跳入了某條except語句,就會執(zhí)行相應(yīng)的異常處理方法(block),執(zhí)行完畢就會結(jié)束。不會再返回try的normal block繼續(xù)執(zhí)行了。
try:a = 1 / 2 #a normal number/variableprint(a)b = 1 / 0 # an abnormal number/variableprint(b)c = 2 / 1 # a normal number/variableprint(c) except:print("Error")結(jié)果是,先打出了一個0,又打出了一個Error。就是把ZeroDivisionError錯誤捕獲了。
先執(zhí)行 try 后面這一堆語句,由上至下:
- step1: a 正常,打印a. 于是打印出0.5 (python3.x以后都輸出浮點(diǎn)數(shù))
- step2: b, 不正常了,0 不能做除數(shù),所以這是一個錯誤。直接跳到except報(bào)錯去。于是打印了Error。
- step3: 其實(shí)沒有step3,因?yàn)槌绦蚪Y(jié)束了。c是在錯誤發(fā)生之后的b語句后才出現(xiàn),根本輪不到執(zhí)行它。也就看不到打印出的c了
但這還不是 try/except 的所有用法
except后面還能跟表達(dá)式的!
所謂的表達(dá)式,就是錯誤的定義。也就是說,我們可以捕捉一些我們想要捕捉的異常。而不是什么異常都報(bào)出來。
異常分為兩類:
- python標(biāo)準(zhǔn)異常
- 自定義異常
我們先拋開自定義異常(因?yàn)樯婕暗筋惖母拍?,看看 except 都能捕捉到哪些 python 標(biāo)準(zhǔn)異常。請查看菜鳥筆記
https://www.runoob.com/python/python-exceptions.html
?
2. try…finallly 語句
用于無論執(zhí)行過程中有沒有異常,都要執(zhí)行清場工作。
try: execution block ##正常執(zhí)行模塊 except A: exc A block ##發(fā)生A錯誤時執(zhí)行 except B: exc B block ##發(fā)生B錯誤時執(zhí)行 except: other block ##發(fā)生除了A,B錯誤以外的其他錯誤時執(zhí)行 else: if no exception, jump to here ##沒有錯誤時執(zhí)行 finally: final block ##總是執(zhí)行tips:?注意順序不能亂,否則會有語法錯誤。如果用 else 就必須有 except,否則會有語法錯誤。
try:a = 1 / 2print(a)print(m) # 拋出 NameError異常, 此后的語句都不在執(zhí)行b = 1 / 0print(b)c = 2 / 1print(c) except NameError:print("Ops!!") # 捕獲到異常 except ZeroDivisionError:print("Wrong math!!") except:print("Error") else:print("No error! yeah!") finally: # 是否異常都執(zhí)行該代碼塊print("Successfully!")?
?
1.?with 語句的原理
?
- 上下文管理協(xié)議(Context Management Protocol):包含方法 __enter__() 和 __exit__(),支持該協(xié)議的對象要實(shí)現(xiàn)這兩個方法。
- 上下文管理器(Context Manager):支持上下文管理協(xié)議的對象,這種對象實(shí)現(xiàn)了 __enter__() 和 __exit__() 方法。上下文管理器定義執(zhí)行 with 語句時要建立的運(yùn)行時上下文,負(fù)責(zé)執(zhí)行 with 語句塊上下文中的進(jìn)入與退出操作。通常使用 with 語句調(diào)用上下文管理器,也可以通過直接調(diào)用其方法來使用。
說完上面兩個概念,我們再從 with 語句的常用表達(dá)式入手,一段基本的 with 表達(dá)式,其結(jié)構(gòu)是這樣的:
with context_expression [as target(s)]:...with-body...其中 context_expression?可以是任意表達(dá)式;as target(s) 是可選的。
with 語句執(zhí)行過程 。在語義上等價(jià)于:
context_manager = context_expressionexit = type(context_manager).__exit__value = type(context_manager).__enter__(context_manager)exc = True # True 表示正常執(zhí)行,即便有異常也忽略;False 表示重新拋出異常,需要對異常進(jìn)行處理try:try:target = value # 如果使用了 as 子句with-body # 執(zhí)行 with-bodyexcept:# 執(zhí)行過程中有異常發(fā)生exc = False# 如果 __exit__ 返回 True,則異常被忽略;如果返回 False,則重新拋出異常# 由外層代碼對異常進(jìn)行處理if not exit(context_manager, *sys.exc_info()):raisefinally:# 正常退出,或者通過 statement-body 中的 break/continue/return 語句退出# 或者忽略異常退出if exc:exit(context_manager, None, None, None)# 缺省返回 None,None 在布爾上下文中看做是 False可以看到,with 和 try finally 有下面的等價(jià)流程:
try: 執(zhí)行 __enter__的內(nèi)容 執(zhí)行 with_block. finally: 執(zhí)行 __exit__內(nèi)容?
那么__enter__和__exit__是怎么用的方法呢?我們直接來看一個栗子好了。
程序無錯的例子:
class Sample(object): # object類是所有類最終都會繼承的類def __enter__(self): # 類中函數(shù)第一個參數(shù)始終是self,表示創(chuàng)建的實(shí)例本身print("In __enter__()")return "Foo"def __exit__(self, type, value, trace):print("In __exit__()")def get_sample():return Sample()with get_sample() as sample:print("sample:", sample)print(Sample) # 這個表示類本身 <class '__main__.Sample'> print(Sample()) # 這表示創(chuàng)建了一個匿名實(shí)例對象 <__main__.Sample object at 0x00000259369CF550>''' In __enter__() sample: Foo In __exit__() <class '__main__.Sample'> <__main__.Sample object at 0x00000226EC5AF550>'''步驟分析:
–> 調(diào)用get_sample()函數(shù),返回Sample類的實(shí)例;
–> 執(zhí)行Sample類中的__enter__()方法,打印"In__enter_()"字符串,并將字符串“Foo”賦值給as后面的sample變量;
–> 執(zhí)行with-block碼塊,即打印"sample: %s"字符串,結(jié)果為"sample: Foo"
–> 執(zhí)行with-block碼塊結(jié)束,返回Sample類,執(zhí)行類方法__exit__()。因?yàn)樵趫?zhí)行with-block碼塊時并沒有錯誤返回,所以type,value,trace這三個arguments都沒有值。直接打印"In__exit__()"
?
程序有錯的例子:
class Sample:def __enter__(self):return selfdef __exit__(self, type, value, trace):print("type:", type)print("value:", value)print("trace:", trace)def do_something(self):bar = 1 / 0return bar + 10with Sample() as sample:sample.do_something()''' type: <class 'ZeroDivisionError'> value: division by zero trace: <traceback object at 0x0000019B73153848> Traceback (most recent call last):File "F:/機(jī)器學(xué)習(xí)/生物信息學(xué)/Code/first/hir.py", line 16, in <module>sample.do_something()File "F:/機(jī)器學(xué)習(xí)/生物信息學(xué)/Code/first/hir.py", line 11, in do_somethingbar = 1 / 0 ZeroDivisionError: division by zero '''步驟分析:
–> 實(shí)例化Sample類,執(zhí)行類方法__enter__(),返回值self也就是實(shí)例自己賦值給sample。即sample是Sample的一個實(shí)例(對象);
–>執(zhí)行with-block碼塊: 實(shí)例sample調(diào)用方法do_something();
–>執(zhí)行do_something()第一行?bar = 1 / 0,發(fā)現(xiàn)ZeroDivisionError,直接結(jié)束with-block代碼塊運(yùn)行
–>執(zhí)行類方法__exit__(),帶入ZeroDivisionError的錯誤信息值,也就是type,value,?trace,并打印它們。
?
如果有多個項(xiàng)目,則會視作存在多個?with?語句嵌套來處理多個上下文管理器:?(?https://docs.python.org/zh-cn/3/reference/compound_stmts.html#the-with-statement )
with A() as a, B() as b:SUITE在語義上等價(jià)于:with A() as a:with B() as b:SUITE在 3.1 版更改:?支持多個上下文表達(dá)式。
參見:PEP 343?- "with" 語句。Python?with?語句的規(guī)范描述、背景和示例。
?
?
2.?自定義上下文管理器
?
開發(fā)人員可以自定義支持上下文管理協(xié)議的類。自定義的上下文管理器要實(shí)現(xiàn)上下文管理協(xié)議所需要的?enter() 和?exit() 兩個方法:
- contextmanager.__enter__()?:進(jìn)入上下文管理器的運(yùn)行時上下文,在語句體執(zhí)行前調(diào)用。with 語句將該方法的返回值賦值給 as 子句中的 target,如果指定了 as 子句的話
-
contextmanager.__exit__(exc_type, exc_value, exc_traceback)?:退出與上下文管理器相關(guān)的運(yùn)行時上下文,返回一個布爾值表示是否對發(fā)生的異常進(jìn)行處理。參數(shù)表示引起退出操作的異常,如果退出時沒有發(fā)生異常,則3個參數(shù)都為None。如果發(fā)生異常時,返回True 表示不處理異常,否則會在退出該方法后重新拋出異常以由 with 語句之外的代碼邏輯進(jìn)行處理。如果該方法內(nèi)部產(chǎn)生異常,則會取代由 statement-body 中語句產(chǎn)生的異常。要處理異常時,不要顯示重新拋出異常,即不能重新拋出通過參數(shù)傳遞進(jìn)來的異常,只需要將返回值設(shè)置為 False 就可以了。之后,上下文管理代碼會檢測是否?__exit__() 失敗來處理異常
下面通過一個簡單的示例來演示如何構(gòu)建自定義的上下文管理器。
注意,上下文管理器必須同時提供?__enter__() 和?__exit__() 方法的定義,缺少任何一個都會導(dǎo)致 AttributeError;with 語句會先檢查是否提供了?__exit__() 方法,然后檢查是否定義了?__enter__() 方法。
# coding = utf-8class DBManager(object):def __init__(self):passdef __enter__(self):print('__enter__')return selfdef __exit__(self, exc_type, exc_val, exc_tb):print('__exit__')return Truedef getInstance():return DBManager()with getInstance() as dbManagerIns:print('with demo')''' 運(yùn)行結(jié)果: __enter__ with demo __exit__ '''with 后面必須跟一個上下文管理器,如果使用了 as,則是把上下文管理器的 __enter__() 方法的返回值賦值給 target,target 可以是單個變量,或者由 "()"?括起來的元組(不能是僅僅由 "," 分隔的變量列表,必須加 "()")
結(jié)果分析:當(dāng)我們使用 with 的時候,__enter__方法被調(diào)用,并且將返回值賦值給 as 后面的變量,并且在退出 with 的時候自動執(zhí)行 __exit__ 方法
class With_work(object):def __enter__(self):"""進(jìn)入with語句的時候被調(diào)用"""print('enter called')return "xxt"def __exit__(self, exc_type, exc_val, exc_tb):"""離開with的時候被with調(diào)用"""print('exit called')with With_work() as as_f:print(f'as_f : {as_f}')print('hello with')''' enter called as_f : xxt hello with exit called '''?
示例 2:
自定義支持 with 語句的對象?
class DummyResource:def __init__(self, tag):self.tag = tagprint(f'Resource [{tag}]')def __enter__(self):print(f'[Enter {self.tag}]: Allocate resource.')return self # 可以返回不同的對象def __exit__(self, exc_type, exc_value, exc_tb):""":param exc_type: 錯誤的類型:param exc_value: 錯誤類型對應(yīng)的值 :param exc_tb: 代碼中錯誤發(fā)生的位置 :return:"""print(f'[Exit {self.tag}]: Free resource.')if exc_tb is None:print(f'[Exit {self.tag}]: Exited without exception.')else:print(f'[Exit {self.tag}]: Exited with exception raised.')return False # 可以省略,缺省的None也是被看做是False# 第一個 with 語句 num = 50 print('*' * num) with DummyResource('First'):print('[with-body] Run without exceptions.') print('*' * num)# 第二個 with 語句 print('*' * num) with DummyResource('second'):print('[with-body] Run with exception.')raise Exceptionprint('[with-body] Run with exception. Failed to finish statement-body!') print('*' * num)# 嵌套 with 語句 print('*' * num) with DummyResource('Normal'):print('[with-body] Run without exceptions.')with DummyResource('With-Exception'):print('[with-body] Run with exception.')raise Exceptionprint('[with-body] Run with exception. Failed to finish statement-body!') print('*' * num)DummyResource 中的?__enter__() 返回的是自身的引用,這個引用可以賦值給 as 子句中的 target 變量;返回值的類型可以根據(jù)實(shí)際需要設(shè)置為不同的類型,不必是上下文管理器對象本身。
__exit__() 方法中對變量 exctb 進(jìn)行檢測,如果不為 None,表示發(fā)生了異常,返回 False 表示需要由外部代碼邏輯對異常進(jìn)行處理;注意到如果沒有發(fā)生異常,缺省的返回值為 None,在布爾環(huán)境中也是被看做 False,但是由于沒有異常發(fā)生,__exit__() 的三個參數(shù)都為 None,上下文管理代碼可以檢測這種情況,做正常處理。
執(zhí)行結(jié)果:
************************************************** Resource [First] [Enter First]: Allocate resource. [with-body] Run without exceptions. [Exit First]: Free resource. [Exit First]: Exited without exception. ************************************************** ************************************************** Resource [second] [Enter second]: Allocate resource. [with-body] Run with exception. [Exit second]: Free resource. [Exit second]: Exited with exception raised. Traceback (most recent call last):File "temp.py", line 30, in <module>raise Exception Exception第1個 with? 語句執(zhí)行結(jié)果:可以看到,正常執(zhí)行時會先執(zhí)行完語句體 with-body,然后執(zhí)行?__exit__() 方法釋放資源。
第2個 with 語句的執(zhí)行結(jié)果:可以看到,with-body 中發(fā)生異常時with-body 并沒有執(zhí)行完,但資源會保證被釋放掉,同時產(chǎn)生的異常由 with 語句之外的代碼邏輯來捕獲處理。
因?yàn)榈?個with語句發(fā)生異常,所以 嵌套 with 語句沒有執(zhí)行。。。
?
?
3. 自動關(guān)閉文件
?
我們都知道打開文件有兩種方法:
- f = open()
- with open() as f:
這兩種方法的區(qū)別就是第一種方法需要我們自己關(guān)閉文件;f.close(),而第二種方法不需要我們自己關(guān)閉文件,無論是否出現(xiàn)異常,with都會自動幫助我們關(guān)閉文件,這是為什么呢?
我們先自定義一個類,用with來打開它:
class Foo(object):def __enter__(self):print("enter called")def __exit__(self, exc_type, exc_val, exc_tb):print("exit called")print("exc_type :%s" % exc_type)print("exc_val :%s" % exc_val)print("exc_tb :%s" % exc_tb)with Foo() as foo:print("hello python")a = 1 / 0print("hello end")''' enter called Traceback (most recent call last): hello python exit called exc_type :<class 'ZeroDivisionError'> exc_val :division by zeroFile "F:/workspaces/python_workspaces/flask_study/with.py", line 25, in <module>a = 1/0 exc_tb :<traceback object at 0x0000023C4EDBB9C8> ZeroDivisionError: division by zeroProcess finished with exit code 1 '''執(zhí)行結(jié)果的輸入順序,分析如下:
當(dāng)我們 with Foo() as foo: 時,此時會執(zhí)行 __enter__方法,然后進(jìn)入執(zhí)行體,也就是:
print("hello python") a = 1/0 print("hello end")語句,但是在 a=1/0 出現(xiàn)了異常,with將會中止,此時就執(zhí)行__exit__方法,就算不出現(xiàn)異常,當(dāng)執(zhí)行體被執(zhí)行完畢之后,__exit__方法仍然被執(zhí)行一次。
我們回到 with open("file")as f: 不用關(guān)閉文件的原因就是在 __exit__ 方法中,存在關(guān)閉文件的操作,所以不用我們手工關(guān)閉文件,with已將為我們做好了這個操作,這就可以理解了。
?
?
4.?contextlib 模塊
?
contextlib --- 為 with語句上下文提供的工具:https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.asynccontextmanager
contextlib 模塊提供了3個對象,使用這些對象,可以對已有的生成器函數(shù)或者對象進(jìn)行包裝,加入對上下文管理協(xié)議的支持,避免了專門編寫上下文管理器來支持 with 語句。
- 裝飾器 contextmanager
- 函數(shù) nested?
- 上下文管理器 closing
?
裝飾器 contextmanager
contextmanager 用于對生成器函數(shù)進(jìn)行裝飾,生成器函數(shù)被裝飾以后,返回的是一個上下文管理器,其?enter() 和?exit() 方法由 contextmanager 負(fù)責(zé)提供,而不再是之前的迭代子。被裝飾的生成器函數(shù)只能產(chǎn)生一個值,否則會導(dǎo)致異常 RuntimeError;產(chǎn)生的值會賦值給 as 子句中的 target,如果使用了 as 子句的話。下面看一個簡單的例子。
from contextlib import contextmanager@contextmanager def demo():print('[Allocate resources]')print('Code before yield-statement executes in __enter__')yield '*** contextmanager demo ***'print('Code after yield-statement executes in __exit__')print('[Free resources]')with demo() as value:print(f'Assigned Value: {value}')''' [Allocate resources] Code before yield-statement executes in __enter__ Assigned Value: *** contextmanager demo *** Code after yield-statement executes in __exit__ [Free resources] '''可以看到,生成器函數(shù)中 yield 之前的語句在?enter() 方法中執(zhí)行,yield 之后的語句在?exit() 中執(zhí)行,而 yield 產(chǎn)生的值賦給了 as 子句中的 value 變量。
需要注意的是,contextmanager 只是省略了?enter() /?exit() 的編寫,但并不負(fù)責(zé)實(shí)現(xiàn)資源的”獲取”和”清理”工作;”獲取”操作需要定義在 yield 語句之前,”清理”操作需要定義 yield 語句之后,這樣 with 語句在執(zhí)行?enter() /?exit() 方法時會執(zhí)行這些語句以獲取/釋放資源,即生成器函數(shù)中需要實(shí)現(xiàn)必要的邏輯控制,包括資源訪問出現(xiàn)錯誤時拋出適當(dāng)?shù)漠惓!?/p>
?
函數(shù) nested
nested 可以將多個上下文管理器組織在一起,避免使用嵌套 with 語句。
nested 語法with nested(A(), B(), C()) as (X, Y, Z):# with-body code here類似于:with A() as X:with B() as Y:with C() as Z:# with-body code here需要注意的是,發(fā)生異常后,如果某個上下文管理器的 exit() 方法對異常處理返回 False, 則更外層的上下文管理器不會監(jiān)測到異常。?
上下文管理器 closing
closing 的實(shí)現(xiàn)如下:
class closing(object):# help doc heredef __init__(self, thing):self.thing = thingdef __enter__(self):return self.thingdef __exit__(self, *exc_info):self.thing.close()上下文管理器會將包裝的對象賦值給 as 子句的 target 變量,同時保證打開的對象在 with-body 執(zhí)行完后會關(guān)閉掉。closing 上下文管理器包裝起來的對象必須提供 close() 方法的定義,否則執(zhí)行時會報(bào) AttributeError 錯誤。
自定義支持 closing 的對象
from contextlib import closingclass ClosingDemo(object):def __init__(self):self.acquire()def acquire(self):print('Acquire resources.')def free(self):print('Clean up any resources acquired.')def close(self):self.free()with closing(ClosingDemo()):print('Using resources')''' Acquire resources. Using resources Clean up any resources acquired. '''closing 適用于提供了 close() 實(shí)現(xiàn)的對象,比如網(wǎng)絡(luò)連接、數(shù)據(jù)庫連接等,也可以在自定義類時通過接口 close() 來執(zhí)行所需要的資源”清理”工作。
?
?
5. 總結(jié)
?
with 是對 try…expect…finally 語法的一種簡化,并且提供了對于異常非常好的處理方式。在Python有2種方式來實(shí)現(xiàn) with 語法:class-based 和 decorator-based,2種方式在原理上是等價(jià)的,可以根據(jù)具體場景自己選擇。
with 最初起源于一種block…as…的語法,但是這種語法被很多人所唾棄,最后誕生了with,關(guān)于這段歷史依然可以去參考PEP-343和PEP-340
with 主要用在:自定義上下文管理器來對軟件系統(tǒng)中的資源進(jìn)行管理,比如數(shù)據(jù)庫連接、共享資源的訪問控制等。文件操作。進(jìn)程線程之間互斥對象。支持上下文其他對象
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Python 中 with 用法详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows 远程执行 cmd 命令的
- 下一篇: 王爽 汇编语言第三版 问题 7.9 将