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

公衆號:pythonislover

一般我們操作文件的時候,大家都會知道我們使用with的方式去寫,一般如下

with open('xxxx/test.txt') as f:

'xxxxxx'

那麼我們爲什麼要使用這種方式去操作文件呢?因爲with這種方式自動幫我們執行了close關閉文件句柄的操作,免的我們忘記關閉句柄,浪費資源。

那我們爲什麼是使用with方式就可以達到這種效果呢?這就是我們今天說的python的上下文管理器的作用。簡單來說上下文管理器必須在這個對象的類中聲明__enter__和__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

with Context('test.txt') as f:

print('主邏輯過程')

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

我們可以看看執行的順序
with觸發__enter__方法
然後執行主邏輯過程
所有程序執行完之後觸發__exit__方法,釋放資源

這就是爲什麼我們用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

Context.get_values(p)

File "C:/Users/aryin/Desktop/mysite2/上下文管理器.py", line 19, in get_values
'c'

print(dic['c']) # 取一個不存在的key


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('會不會執行我們') #主邏輯

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

'c'

會不會執行我們

我們可以看到程序沒有奔潰,我們執行在__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'>

'_io.TextIOWrapper' object has no attribute 'wasdf'

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

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.提高代碼的可讀性,簡潔性等等

好文推薦:
Python基礎-不一樣的切片操作 - https://mp.weixin.qq.com/s/3pute7-xrdhAt2Yk41nw9w
Python那些有用的小知識點1 - https://mp.weixin.qq.com/s/JXnBrYj0TEAo9LNrVv2cPA
Python日誌模塊之你還在用PRINT打印日誌嗎 - https://mp.weixin.qq.com/s/P_SDxmbxkK1ei3-sq5Qhlw
90%的人說Python程序慢,5大神招讓你的代碼像賽車一樣跑起來 - https://mp.weixin.qq.com/s/XZcqzwodPjvjltPRNRTpVQ
110道python面試題 - https://mp.weixin.qq.com/s/SyC_LLQL8AU3i6wYNlOdNQ
Python你想知道的時間格式Time都在這裏 - https://mp.weixin.qq.com/s/fXv9leS8xsKUEVRvo8sKdA
深度辨析 Python 的 eval() 與 exec() - https://mp.weixin.qq.com/s/Dkn6TXUSfEk_Lg4SF0m81g
爲什麼range不是迭代器?range到底是什麼類型? - https://mp.weixin.qq.com/s/9FdWqAuQgv-t-yLJXwiHTg
6種方法讓Python遍歷多個列表 - https://mp.weixin.qq.com/s/z1wuXp8x08YZQMmKIMQHQg
Python之sorted,max,min內置函數用法 - https://mp.weixin.qq.com/s/KDRedycdz3qAGsaFMrqJBg
愉快地遷移到Python3 - https://mp.weixin.qq.com/s/RPdx2G36kDIQ3_G_XPk9tQ

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章