1. 程式人生 > 程式設計 >詳解python with 上下文管理器

詳解python with 上下文管理器

作為一個 Java 為母語的程式設計師來講,學習起其他新的語言就難免任何事都與 Java 進行橫向對比。Java 7 引入了能省去許多重複程式碼的 try-with-resources 特性,不用每回 try/finally 來釋放資源(不便之處有區域性變數必須宣告在 try 之前,finally 裡還要巢狀 try/catch 來處理異常)。比如下面的 Java 程式碼

try(InputStream inputStream = new FileInputStream("abc.txt")) {
  System.out.println(inputStream.read());
} catch (Exception ex) {
}

它相應的不使用 try-with-resources 語法的程式碼就是

InputStream inputStream = null;
try {
  inputStream = new FileInputStream("abc.txt");
} catch (Exception ex) {
} finally {
  if(inputStream != null) {
    try {
      inputStream.close();
    } catch (Exception ex) {
    }
  }
}

類似的 Python 也有自己的 try-with-resources 寫法,就是 with 關鍵字,它的概念叫做上下文管理器(Context Manager)。

with 關鍵字的使用

with open('some_file','w') as opened_file:
  opened_file.write('Hola!')

以上的程式碼相當於

opened_file = open('some_file','w')
try:
  opened_file.write('Hola!')
finally:
  opened_file.close()

也就是 with 關鍵字開啟的資源會在 with 語句塊結束後自動呼叫相應的方法自動釋放(無論 with 中操作是否有異常)。

with 用起來是很方便的,但是什麼樣的資源可以用 with 關鍵字?Python 是怎麼知道要呼叫哪個方法來關閉資源的?進而如何實現自己的支援上下文管理器的 Python 類。

再次回顧 Java 的 try-with-resources 語法,try(...) 括號支援的類必須是實現了 AutoCloseable 介面,它的介面方法是

public void close() throws IOException

也就是 Java 的 try-with-resources 語法會自動呼叫以上方法來釋放資源,要實現可被自動釋放的 Java 就只須遵照這一規則就行。

而在 Python 中,能被 with 的類有兩種實現方式

實現基本方法以支援上下文管理器的類

一個 Python 類要能被用於 with 上下文,必須實現至少 __enter__ __exit__ 方法。這兩個方法的意思好理解,一個是建立資源後,後者是退出 with 語句塊後。請看下面的例子

class File(object):
  def __init__(self,file_name,method):
    self.file_obj = open(file_name,method)
 
  def __enter__(self):
    print("---enter")
    return self.file_obj
 
  def __exit__(self,type,value,traceback):
    print("---exit")
    self.file_obj.close()
 
 
with File('data.txt','r') as data_file:
  print(data_file.read())

假設 data.txt 檔案中的內容是

hello
world

那麼以上程式執行後的輸出就是

--enter
hello
world
---exit

  1. __enter__ 返回的值作為 with ... as data_file 中的 data_file 變數的值,如果 __enter__ 沒有返回,data_file 得到的就是 NoneType object 了。
  2. __exit__ 可利用來釋放資源
  3. 沒有 __enter__ 方法試圖用 with 的寫法執行時會得到 AttributeErro: __enter__ 異常
  4. 同樣,沒有 __exit__ 方法試圖用 with 的寫法執行時會得到 AttributeErro: __exit__ 異常
  5. __exit__ 有其他額外的三個引數,可獲得資源的值,以及能處理 with 塊中執行出現異常的情況
  6. __exit__ 的返回值也有用途,如果它返回 True 則出現的異常不再向外傳播,其他值的話直接向外拋

利用生成器(Generator) 和裝飾器建立支援上下文管理器的方法

此種方式比較簡單,不過邏輯控制上沒有這麼強。

from contextlib import contextmanager
 
@contextmanager
def open_file(name,method):
  f = open(name,method)
  yield f
  f.close()

使用 f 的執行程式碼將被放置在 yield f 所處的位置,with 使用以上方法。yield 後的 f 變數將是 with...as 後的變數值

with open_file('some_file','w') as file_object:
  file_object.write('hola!')

這裡也要注意異常處理的情況,比如把上面程式碼開啟檔案的模式換作 r,仍然試圖去寫檔案,這樣在 open_file 方法的 yield f 位置將產生異常,會造成 f.close() 得不到執行,不能正確釋放該資源。

欲更具防禦性,前面的 yield f 可以擴充套件也如下的形式

try:
  yield f
except Exception as ex:
  pass #處理異常,或繼續向外拋
finally:
  f.close()

@contextmanager 裝飾器內部也是封裝為一個實現了 __enter__ __exit__ 方法的物件。

參考連結:Context Managers

以上就是詳解python with 上下文管理器的詳細內容,更多關於python with 上下文管理器的資料請關注我們其它相關文章!