python语句-浅谈 Python 的 with 语句
引言
with 語句是從 Python 2.5 開始引入的一種與異常處理相關(guān)的功能(2.5 版本中要通過 from future import with_statement 導(dǎo)入后才可以使用),從 2.6 版本開始缺省可用(參考 What’s new in Python 2.6? 中 with 語句相關(guān)部分介紹)。with 語句適用于對資源進(jìn)行訪問的場合,確保不管使用過程中是否發(fā)生異常都會執(zhí)行必要的”清理”操作,釋放資源,比如文件使用后自動關(guān)閉、線程中鎖的自動獲取和釋放等。
術(shù)語
要使用 with 語句,首先要明白上下文管理器這一概念。有了上下文管理器,with 語句才能工作。
下面是一組與上下文管理器和with 語句有關(guān)的概念。
上下文管理協(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)用其方法來使用。
運(yùn)行時上下文(runtime context) :由上下文管理器創(chuàng)建,通過上下文管理器的 enter() 和 exit() 方法實(shí)現(xiàn),enter() 方法在語句體執(zhí)行之前進(jìn)入運(yùn)行時上下文,exit() 在語句體執(zhí)行完后從運(yùn)行時上下文退出。with 語句支持運(yùn)行時上下文這一概念。
上下文表達(dá)式(Context Expression) :with 語句中跟在關(guān)鍵字 with 之后的表達(dá)式,該表達(dá)式要返回一個上下文管理器對象。
語句體(with-body) :with 語句包裹起來的代碼塊,在執(zhí)行語句體之前會調(diào)用上下文管理器的 enter() 方法,執(zhí)行完語句體之后會執(zhí)行 exit() 方法。
基本語法和工作原理
with 語句的語法格式如下:
清單 1. with 語句的語法格式
with context_expression [as target(s)]:
with-bodyShow moreShow more icon
這里 contextexpression 要返回一個上下文管理器對象,該對象并不賦值給 as 子句中的 target(s) ,如果指定了 as 子句的話,會將上下文管理器的 _enter() 方法的返回值賦值給 target(s)。target(s) 可以是單個變量,或者由”()”括起來的元組(不能是僅僅由”,”分隔的變量列表,必須加”()”)。
Python 對一些內(nèi)建對象進(jìn)行改進(jìn),加入了對上下文管理器的支持,可以用于 with 語句中,比如可以自動關(guān)閉文件、線程鎖的自動獲取和釋放等。假設(shè)要對一個文件進(jìn)行操作,使用 with 語句可以有如下代碼:
清單 2. 使用 with 語句操作文件對象
with open(r"somefileName") as somefile:
for line in somefile:
print line
# ...more codeShow moreShow more icon
這里使用了 with 語句,不管在處理文件過程中是否發(fā)生異常,都能保證 with 語句執(zhí)行完畢后已經(jīng)關(guān)閉了打開的文件句柄。如果使用傳統(tǒng)的 try/finally 范式,則要使用類似如下代碼:
清單 3. try/finally 方式操作文件對象
somefile = open(r"somefileName")
try:
for line in somefile:
print line
# ...more code
finally:
somefile.close()Show moreShow more icon
比較起來,使用 with 語句可以減少編碼量。已經(jīng)加入對上下文管理協(xié)議支持的還有模塊 threading、decimal 等。
PEP 0343 對 with 語句的實(shí)現(xiàn)進(jìn)行了描述。with 語句的執(zhí)行過程類似如下代碼塊:
清單 4. with 語句執(zhí)行過程
context_manager = context_expression
exit = 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-body
except:
# 執(zhí)行過程中有異常發(fā)生
exc = False
# 如果 __exit__ 返回 True,則異常被忽略;如果返回 False,則重新拋出異常
# 由外層代碼對異常進(jìn)行處理
if not exit(context_manager, *sys.exc_info()):
raise
finally:
# 正常退出,或者通過 statement-body 中的 break/continue/return 語句退出
# 或者忽略異常退出
if exc:
exit(context_manager, None, None, None)
# 缺省返回 None,None 在布爾上下文中看做是 FalseShow moreShow more icon
執(zhí)行 context_expression,生成上下文管理器 context_manager
調(diào)用上下文管理器的 enter() 方法;如果使用了 as 子句,則將 enter() 方法的返回值賦值給 as 子句中的 target(s)
執(zhí)行語句體 with-body
不管是否執(zhí)行過程中是否發(fā)生了異常,執(zhí)行上下文管理器的 exit() 方法,exit() 方法負(fù)責(zé)執(zhí)行”清理”工作,如釋放資源等。如果執(zhí)行過程中沒有出現(xiàn)異常,或者語句體中執(zhí)行了語句 break/continue/return,則以 None 作為參數(shù)調(diào)用 exit(None, None, None) ;如果執(zhí)行過程中出現(xiàn)異常,則使用 sys.excinfo 得到的異常信息為參數(shù)調(diào)用 _exit(exc_type, exc_value, exc_traceback)
出現(xiàn)異常時,如果 exit(type, value, traceback) 返回 False,則會重新拋出異常,讓with 之外的語句邏輯來處理異常,這也是通用做法;如果返回 True,則忽略異常,不再對異常進(jìn)行處理
自定義上下文管理器
開發(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() 方法。
假設(shè)有一個資源 DummyResource,這種資源需要在訪問前先分配,使用完后再釋放掉;分配操作可以放到 enter() 方法中,釋放操作可以放到 exit() 方法中。簡單起見,這里只通過打印語句來表明當(dāng)前的操作,并沒有實(shí)際的資源分配與釋放。
清單 5. 自定義支持 with 語句的對象
class DummyResource:
def __init__(self, tag):
self.tag = tag
print "Resource [%s]" % tag
def __enter__(self):
print "[Enter %s]: Allocate resource." % self.tag
return self # 可以返回不同的對象
def __exit__(self, exc_type, exc_value, exc_tb):
print "[Exit %s]: Free resource." % self.tag
if exc_tb is None:
print "[Exit %s]: Exited without exception." % self.tag
else:
print "[Exit %s]: Exited with exception raised." % self.tag
return False # 可以省略,缺省的None也是被看做是FalseShow moreShow more icon
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,上下文管理代碼可以檢測這種情況,做正常處理。
下面在 with 語句中訪問 DummyResource :
清單 6. 使用自定義的支持 with 語句的對象
with DummyResource("Normal"):
print "[with-body] Run without exceptions."
with DummyResource("With-Exception"):
print "[with-body] Run with exception."
raise Exception
print "[with-body] Run with exception. Failed to finish statement-body!"Show moreShow more icon
第1個 with 語句的執(zhí)行結(jié)果如下:
清單 7. with 語句1執(zhí)行結(jié)果
Resource [Normal]
[Enter Normal]: Allocate resource.
[with-body] Run without exceptions.
[Exit Normal]: Free resource.
[Exit Normal]: Exited without exception.Show moreShow more icon
可以看到,正常執(zhí)行時會先執(zhí)行完語句體 with-body,然后執(zhí)行 exit() 方法釋放資源。
第2個 with 語句的執(zhí)行結(jié)果如下:
清單 8. with 語句2執(zhí)行結(jié)果
Resource [With-Exception]
[Enter With-Exception]: Allocate resource.
[with-body] Run with exception.
[Exit With-Exception]: Free resource.
[Exit With-Exception]: Exited with exception raised.
Traceback (most recent call last):
File "G:/demo", line 20, in
raise Exception
ExceptionShow moreShow more icon
可以看到,with-body 中發(fā)生異常時with-body 并沒有執(zhí)行完,但資源會保證被釋放掉,同時產(chǎn)生的異常由 with 語句之外的代碼邏輯來捕獲處理。
可以自定義上下文管理器來對軟件系統(tǒng)中的資源進(jìn)行管理,比如數(shù)據(jù)庫連接、共享資源的訪問控制等。Python 在線文檔 Writing Context Managers 提供了一個針對數(shù)據(jù)庫連接進(jìn)行管理的上下文管理器的簡單范例。
contextlib 模塊
contextlib 模塊提供了3個對象:裝飾器 contextmanager、函數(shù) nested 和上下文管理器 closing。使用這些對象,可以對已有的生成器函數(shù)或者對象進(jìn)行包裝,加入對上下文管理協(xié)議的支持,避免了專門編寫上下文管理器來支持 with 語句。
裝飾器 contextmanager
contextmanager 用于對生成器函數(shù)進(jìn)行裝飾,生成器函數(shù)被裝飾以后,返回的是一個上下文管理器,其 enter() 和 exit() 方法由 contextmanager 負(fù)責(zé)提供,而不再是之前的迭代子。被裝飾的生成器函數(shù)只能產(chǎn)生一個值,否則會導(dǎo)致異常 RuntimeError;產(chǎn)生的值會賦值給 as 子句中的 target,如果使用了 as 子句的話。下面看一個簡單的例子。
清單 9. 裝飾器 contextmanager 使用示例
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 "Assigned Value: %s" % valueShow moreShow more icon
結(jié)果輸出如下:
清單 10. contextmanager 使用示例執(zhí)行結(jié)果
[Allocate resources]
Code before yield-statement executes in __enter__
Assigned Value: *** contextmanager demo ***
Code after yield-statement executes in __exit__
[Free resources]Show moreShow more icon
可以看到,生成器函數(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 語句。
清單 11. nested 語法
with nested(A(), B(), C()) as (X, Y, Z):
# with-body code hereShow moreShow more icon
類似于:
清單 12. nested 執(zhí)行過程
with A() as X:
with B() as Y:
with C() as Z:
# with-body code hereShow moreShow more icon
需要注意的是,發(fā)生異常后,如果某個上下文管理器的 exit() 方法對異常處理返回 False,則更外層的上下文管理器不會監(jiān)測到異常。
上下文管理器 closing
closing 的實(shí)現(xiàn)如下:
清單 13. 上下文管理 closing 實(shí)現(xiàn)
class closing(object):
# help doc here
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()Show moreShow more icon
上下文管理器會將包裝的對象賦值給 as 子句的 target 變量,同時保證打開的對象在 with-body 執(zhí)行完后會關(guān)閉掉。closing 上下文管理器包裝起來的對象必須提供 close() 方法的定義,否則執(zhí)行時會報(bào) AttributeError 錯誤。
清單 14. 自定義支持 closing 的對象
class 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"Show moreShow more icon
結(jié)果輸出如下:
清單 15. 自定義 closing 對象的輸出結(jié)果
Acquire resources.
Using resources
Clean up any resources acquired.Show moreShow more icon
closing 適用于提供了 close() 實(shí)現(xiàn)的對象,比如網(wǎng)絡(luò)連接、數(shù)據(jù)庫連接等,也可以在自定義類時通過接口 close() 來執(zhí)行所需要的資源”清理”工作。
結(jié)束語
本文對 with 語句的語法和工作機(jī)理進(jìn)行了介紹,并通過示例介紹了如何實(shí)現(xiàn)自定義的上下文管理器,最后介紹了如何使用 contextlib 模塊來簡化上下文管理器的編寫。
總結(jié)
以上是生活随笔為你收集整理的python语句-浅谈 Python 的 with 语句的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java并发实战编程pdf_「原创」Ja
- 下一篇: 10备忘录没有表格_iPhone的【备忘