標籤:

上下文管理器-1.基本概念及其實現

上下文管理器-1.基本概念及其實現

來自專欄 python菜籃

概要

本文是上下文管理器專題第一篇,主要簡單介紹上下文及上下文管理器的基本概念,分析with操作符的使用及其優缺點,最後還介紹了如何實現自定義上下文管理器

什麼是上下文

程序中所謂的上下文件就是指程序所執行的環境狀態,或者說程序運行的情景

上下文管理器定義

寫代碼時,我們希望把一些操作放到一個代碼塊中,這樣在代碼塊中執行時就可以保持在某種運行狀態,而當離開該代碼塊時就執行另一個操作,結束當前狀態;所以,簡單來說,上下文管理器的目的就是規定對象的使用範圍,如果超出範圍就採取「處理」,這一功能是在Python3.5之後引進的,它的優勢在於可以使得你的代碼更具可讀性,且不容易出錯。

常見的上下文管理器:with操作符

最常見的上下文管理器就是with語句了,python提供了with語句語法,來構建對資源的自動創建與自動釋放,允許你在有需要的時候,精確地分配和釋放資源

使用方法:

首先我們來看一段未使用with語句的代碼,看看它在運行過程中會出現什麼問題?

#when without using context managerf = open("hl.txt", "w")print(f.closed) #運行到這裡,會列印出False,因為沒有關閉f.write("Hello,context manager!")f.close()print(f.closed) #這裡才會列印出True,表示文件關閉

表面上看似這段代碼也並沒什麼大的問題,但如果這段代碼根本執行不到f.close()這一句就會出現問題,假設我們在寫入的過程中,磁碟滿了,代碼就會在寫入的那一句出現異常,這樣就永遠執行不到f.close()這一句,文件就永遠不會被關閉,當然也可以用try…finally語句,如下所示:

#when using try…finallytry: f = open("hl.txt", "w") print(f.closed) #運行到這裡,會列印出False,因為沒有關閉 f.write("Hello,context manager!")finally: f.close()print(f.closed) #這裡才會列印出True,表示文件關閉

但是同樣存在問題,當我們執行的不是簡單的寫入操作,而是其他更複雜的操作,如拷貝,同時讀和寫,try…finally語句根本無法保證代碼的美感。這時,with就派上用場了。

下面,讓我們來看一下使用了with的情況

#when using context managerwith open("hl.txt","w") as f: print(f.closed) #False f.write("Hello,context manager!")print(f.closed) #True 無需執行f.close()

上面代碼中,可以看到一個代碼塊:with…as…

其中,with關鍵詞總是伴隨著上下文管理器出現,as關鍵詞將open(「hl.txt」,」w」)賦給了新的對象f,使得在該代碼塊中可以對f執行任意操作,代碼相當簡潔。

使用with的優點:

避免了瑣碎操作:通過使用with,許多樣板代碼可以被消掉

避免了遺忘步驟:因此不用關注嵌套代碼如何退出,又能確保我們的文件會被關閉

實現自定義上下文管理器

實現了上下文協議的函數/對象即為上下文管理器,上下文管理協議則是由enterexit構成

要實現上下文管理器可以有兩種方式,一種為基於類的實現,一種為基於生成器的實現

基於類的實現:

基於類的實現需要實現兩個方法:enter()和_exit_()

enter():負責進入代碼塊的準備工作,進入前被調用;

exit():負責離開代碼塊的善後工作,離開後被調用;

class File(object): def __init__(self, file_name, method): self.file_obj = open(file_name, method) def __enter__(self): return self.file_obj def __exit__(self, type, value, traceback): self.file_obj.close()

任何定義了_enter_()和_exit_()方法的對象都可以用於上下文管理器,通過定義enterexit方法,我們可以在with語句里使用它

with File(demo.txt, w) as opened_file: opened_file.write(Hola!)

我們的exit函數接受三個參數。這些參數對於每個上下文管理器類中的exit方法都是必須的。我們來看看在底層都發生了什麼。

1. with語句先調用File類的__enter__方法

2. __enter__方法打開文件並返回給with語句

3. 打開的文件句柄被傳遞給opened_file參數

4. 我們使用.write()來寫文件

5. with語句調用__exit__方法關閉文件。

處理異常

如果發生異常,Python會將異常的type,value和traceback傳遞給exit方法

嘗試訪問文件對象的一個不支持的方法

with File(demo.txt, w) as opened_file: opened_file.undefined_function(Hola!)

exit方法返回的是None(如果沒有return語句那麼方法會返回None)。因此,with語句拋出了那個異常。

Traceback (most recent call last): File "<stdin>", line 2, in <module>AttributeError: file object has no attribute undefined_function

當異常發生時,with語句會採取下面4步。

1. 它把異常的type,value和traceback傳遞給exit方法。

2. 它讓exit方法來處理異常

3. 如果exit返回的是True,那麼這個異常就被優雅地處理了。

4. 如果exit返回的是True以外的任何東西,那麼這個異常將被with語句拋出

exit方法中處理異常:

class File(object): def __init__(self, file_name, method): self.file_obj = open(file_name, method) def __enter__(self): return self.file_obj def __exit__(self, type, value, traceback): print("Exception has been handled") self.file_obj.close() return Truewith File(demo.txt, w) as opened_file: opened_file.undefined_function()

exit方法返回了True,因此沒有異常會被with語句拋出

基於生成器的實現:

我們還可以用裝飾器(decorators)和生成器(generators)來實現上下文管理器

Python有個contextlib模塊專門用於這個目的。我們可以使用一個生成器函數來實現一個上下文管理器,而不是使用一個類。

from contextlib import contextmanager@contextmanagerdef file_open(path): try: f_obj = open(path,"w") yield f_obj except OSError: print("We had an error!") finally: print("Closing file") f_obj.close()if __name__ == "__main__": with file_open("test/test.txt") as fobj: fobj.write("Testing context managers")

在這裡,我們從contextlib模塊中引入contextmanager,然後裝飾我們所定義的file_open函數。這就允許我們使用Python的with語句來調用file_open函數。在函數中,我們打開文件,然後通過yield,將其傳遞出去,最終主調函數可以使用它。

一旦with語句結束,控制就會返回給file_open函數,它繼續執行yield語句後面的代碼。這個最終會執行finally語句--關閉文件。如果我們在打開文件時遇到了OSError錯誤,它就會被捕獲,最終finally語句依然會關閉文件句柄。


推薦閱讀:

數據分析的python基底(4)建立數據分析的流程
空間數據可視化筆記——simple features空間對象基礎
黃哥Python每日新聞(2017-8-9)
python isinstance 函數

TAG:Python |