一篇文章告訴你Python上下文管理器怎麼用

通常咱們操做文件的時候,你們都會知道咱們使用with的方式去寫,通常以下python

with open('xxxx/test.txt') as f: 'xxxxxx'數據庫

那麼咱們爲何要使用這種方式去操做文件呢?由於with這種方式自動幫咱們執行了close關閉文件句柄的操做,免的咱們忘記關閉句柄,浪費資源。編程

那咱們爲何是使用with方式就能夠達到這種效果呢?這就是咱們今天說的python的上下文管理器的做用。簡單來講上下文管理器必須在這個對象的類中聲明__enter__和__exit__方法app

下面咱們看看怎麼定義本身的上下文管理器:函數

class Context: def init(self,name): self.name=namespa

# 下面使用with語句, 對象的__enter__被觸發, 返回值則賦值給as聲明的變量,這裏就是f
def __enter__(self):
    print('自定義上下文管理器')
    # return self
#with中代碼塊執行完畢時觸發執行,用來釋放資源,例如文件句柄,數據庫鏈接等
def __exit__(self, exc_type, exc_val, exc_tb):
    print('程序運行結束,釋放資源')
    print(exc_type)
    print(exc_val)
    print(exc_tb)
    # return True
複製代碼

with Context('test.txt') as f: print('主邏輯過程')code

結果: 自定義上下文管理器 主邏輯過程 程序運行結束,釋放資源 None None None對象

咱們能夠看看執行的順序 with觸發__enter__方法 而後執行主邏輯過程 全部程序執行完以後觸發__exit__方法,釋放資源utf-8

這就是爲何咱們用with方法操做文件時候不用f.close的緣由,由於在__exit__方法中幫咱們自動釋放了文件句柄資源

exit()中的三個參數分別表明異常類型,異常值和追溯信息,with語句中代碼塊出現異常,則with後的代碼都沒法執行

下面咱們在類中加入異常代碼塊,看看什麼狀況,是否with後的代碼都沒法執行

class Context: def init(self,name): self.name=name

# 下面使用with語句, 對象的__enter__被觸發, 返回值則賦值給as聲明的變量,這裏就是f
def __enter__(self):
    print('自定義上下文管理器')
    # return self
#with中代碼塊執行完畢時觸發執行,用來釋放資源,例如文件句柄,數據庫鏈接等
def __exit__(self, exc_type, exc_val, exc_tb):
    print('程序運行結束,釋放資源')
    print(exc_type)
    print(exc_val)
    print(exc_tb)
    # return True
def  get_values(self):
    dic = {'a':1,'b':2}
    print(dic['c']) # 取一個不存在的key
複製代碼

p = Context('test.txt') with Context('test.txt') as f: print('主邏輯過程') Context.get_values(p) print('會不會執行咱們') #主邏輯

結果:

自定義上下文管理器 主邏輯過程 Traceback (most recent call last): 程序運行結束,釋放資源 File "C:/Users/aryin/Desktop/mysite2/上下文管理器.py", line 24, in <class 'KeyError'> Context.get_values(p) File "C:/Users/aryin/Desktop/mysite2/上下文管理器.py", line 19, in get_values 'c' print(dic['c']) # 取一個不存在的key <traceback object at 0x000001E50A7C04C8> KeyError: 'c'

能夠看到報出KeyError,這也是咱們預測到的,主邏輯也確實沒有執行(print('會不會執行咱們') 沒有執行),那咱們怎麼利用__exit__中的三個參數捕獲異常而且實現代碼繼續執行呢?

class Context: def init(self,name): self.name=name

# 下面使用with語句, 對象的__enter__被觸發, 返回值則賦值給as聲明的變量,這裏就是f
def __enter__(self):
    print('自定義上下文管理器')
    # return self
#with中代碼塊執行完畢時觸發執行,用來釋放資源,例如文件句柄,數據庫鏈接等
def __exit__(self, exc_type, exc_val, exc_tb):
    print('程序運行結束,釋放資源')
    print(exc_type)
    print(exc_val)
    print(exc_tb)
    return True  #新添加這句
def  get_values(self):
    dic = {'a':1,'b':2}
    print(dic['c']) # 取一個不存在的key
複製代碼

p = Context('test.txt') with Context('test.txt') as f: print('主邏輯過程') Context.get_values(p) print('會不會執行咱們') #主邏輯

結果: 自定義上下文管理器 主邏輯過程 程序運行結束,釋放資源 <class 'KeyError'> 'c' <traceback object at 0x00000187D3EC0488> 會不會執行咱們

咱們能夠看到程序沒有奔潰,咱們執行在__exit__函數中增長了reture True,實現異常被捕獲,而且主邏輯繼續執行(print('會不會執行咱們')) ,也就是說若是__exit()返回值爲True,那麼異常會被清空,就好像啥都沒發生同樣,with後的語句正常執行。

咱們可使用with的上下文管理器更優雅的處理代碼的中的異常捕獲,而不用try..execpt..這種方法去捕獲,而且能夠實現,程序執行完畢以後,咱們想操做的一些資源釋放操做,這樣增長了代碼的簡潔程度和可讀性。

下面咱們看看一個文件的寫入的With案例

class Context: def init(self,filepath,mode='r',encoding='utf-8'): self.filepath=filepath self.mode=mode self.encoding=encoding

def __enter__(self):
    # print('enter')
    self.f=open(self.filepath,mode=self.mode,encoding=self.encoding)
    return self.f

def __exit__(self, exc_type, exc_val, exc_tb):
    # print('exit')
    print(exc_type)
    print(exc_val)
    print(exc_tb)
    self.f.close()
    return True
def __getattr__(self, item):
    return getattr(self.f,item)
複製代碼

with Context('file_read.txt','w') as f: print(f) f.write('testfile') #正常寫入 f.xxxxx #拋出異常,__exit__處理捕獲異常,而且釋放文件句柄

結果: <_io.TextIOWrapper name='file_read.txt' mode='w' encoding='utf-8'> <class 'AttributeError'> '_io.TextIOWrapper' object has no attribute 'wasdf' <traceback object at 0x0000019C630D0488>

下面介紹另一種實現上下文管理器的方式,上面咱們經過類的方式去實現,下面咱們經過方法加裝飾器的方式實現,具體的原理上面說過了,這裏就不說了,這種方法也許更加歡迎。

import contextlib

#裝飾器 @contextlib.contextmanager def context(filepath,mode='r',encoding='utf-8'):

#這段相關於__enter__方法
print('這是__enter__')
f = open(filepath, mode=mode,encoding=encoding)

try:
    yield f
except Exception as e:  #捕獲異常
    print(e)
finally:

    # 這段相關於__exit__方法
    print('這是__exit__')
    f.close()

    return True
複製代碼

#下面用法相同 with context('file_read.txt','w') as f: print(f) f.write('testfile') #正常寫入 f.xxxxx #拋出異常,__exit__處理捕獲異常,而且釋放文件句柄

最後咱們說說Python的優勢吧

1.使用with語句的目的就是把代碼塊放入with中執行,with結束後,自動完成清理工做,無須手動干預 2.在須要管理一些資源好比文件,數據庫鏈接和鎖的編程環境中,能夠在__exit__中定製自動釋放資源的機制 3.提升代碼的可讀性,簡潔性等等

相關文章
相關標籤/搜索