1. 程式人生 > >淺談with語句

淺談with語句

上下文管理器

要使用 with 語句,首先要明白上下文管理器這一概念。有了上下文管理器,with 語句才能工作。

上下文管理協議(Context Management Protocol)
包含方法 enter() 和 exit(),支援該協議的物件要實現這兩個方法。

上下文管理器(Context Manager)
支援上下文管理協議的物件,這種物件實現了enter() 和 exit() 方法。上下文管理器定義執行 with 語句時要建立的執行時上下文,負責執行 with 語句塊上下文中的進入與退出操作。通常使用 with 語句呼叫上下文管理器,也可以通過直接呼叫其方法來使用。

執行時上下文(runtime context)


由上下文管理器建立,通過上下文管理器的 enter() 和exit() 方法實現,enter() 方法在語句體執行之前進入執行時上下文,exit() 在語句體執行完後從執行時上下文退出。with 語句支援執行時上下文這一概念。

上下文表達式(Context Expression)
with 語句中跟在關鍵字 with 之後的表示式,該表示式要返回一個上下文管理器物件。

語句體(with-body)
with 語句包裹起來的程式碼塊,在執行語句體之前會呼叫上下文管理器的 enter() 方法,執行完語句體之後會執行 exit() 方法。

context_manager._enter_

()
進入上下文管理器的執行時上下文,在語句體執行前呼叫。with 語句將該方法的返回值賦值給 as 子句中的 target,如果指定了 as 子句的話。

context_manager._exit_(exc_type, exc_value, exc_traceback)
退出與上下文管理器相關的執行時上下文,返回一個布林值表示是否對發生的異常進行處理。引數表示引起退出操作的異常,如果退出時沒有發生異常,則3個引數都為None。如果發生異常,返回True 表示不處理異常,否則會在退出該方法後重新丟擲異常以由 with 語句之外的程式碼邏輯進行處理。如果該方法內部產生異常,則會取代由 statement-body 中語句產生的異常。要處理異常時,不要顯示重新丟擲異常,即不能重新丟擲通過引數傳遞進來的異常,只需要將返回值設定為 False 就可以了。之後,上下文管理程式碼會檢測是否 exit

() 失敗來處理異常。

基本語法

with 語句的語法格式

with context_expression [as target(s)]:
    with-body

這裡 context_expression 要返回一個上下文管理器物件,該物件並不賦值給 as 子句中的 target(s) ,如果指定了 as 子句的話,會將上下文管理器的 enter() 方法的返回值賦值給 target(s)。target(s) 可以是單個變數,或者由“()”括起來的元組(不能是僅僅由“,”分隔的變數列表,必須加“()”)。

使用 with 語句操作檔案物件
Python 對一些內建物件進行改進,加入了對上下文管理器的支援,可以用於 with 語句中,比如可以自動關閉檔案、執行緒鎖的自動獲取和釋放等。假設要對一個檔案進行操作,使用 with 語句可以有如下程式碼:

with open(r'somefileName') as somefile:
    for line in somefile:
        print line
        # ...more code

這裡使用了 with 語句,不管在處理檔案過程中是否發生異常,都能保證 with 語句執行完畢後已經關閉了開啟的檔案控制代碼。

try/finally 方式操作檔案物件
如果使用傳統的 try/finally 正規化,則要使用類似如下程式碼:

    somefile = open(r'somefileName')
    try:
        for line in somefile:
            print line
            # ...more code
    finally:
        somefile.close()

with 語句執行過程

context_manager = context_expression
exit = type(context_manager).__exit__
value = type(context_manager).__enter__(context_manager)

try:exc = True   #//True 表示正常執行,即便有異常也忽略;False 表示重新丟擲異常,需要對異常進行處理

    try:
        target = value  #//如果使用了as子句
        with-body       #//執行with-body
    except:
        #//執行過程中有異常發生
        exc = False
        #//如果__exit__ 返回True,則異常被忽略;如果返回False,則重新丟擲異常
        #//由外層程式碼對異常進行處理
        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在布林上下文中看做是False

1.執行 context_expression,生成上下文管理器 context_manager

2.呼叫上下文管理器的 enter() 方法;如果使用了 as 子句,則將 enter() 方法的返回值賦值給 as 子句中的 target(s)

3.執行語句體 with-body

4.不管是否執行過程中是否發生了異常,執行上下文管理器的 exit() 方法,exit() 方法負責執行“清理”工作,如釋放資源等。如果執行過程中沒有出現異常,或者語句體中執行了語句 break/continue/return,則以 None 作為引數呼叫 exit(None, None, None) ;如果執行過程中出現異常,則使用 sys.exc_info 得到的異常資訊為引數呼叫 exit(exc_type, exc_value, exc_traceback)

5.出現異常時,如果 exit(type, value, traceback) 返回 False,則會重新丟擲異常,讓with 之外的語句邏輯來處理異常,這也是通用做法;如果返回 True,則忽略異常,不再對異常進行處理