操作文件對象時可以:
with open('a.txt') as f:
'代碼塊'
上述叫做上下文管理協議,即with語句。
想象一下,你有兩個需要結對執行的相關操作,然后,還要在他們中間放置一段代碼。比如打開一個文件,操作文件,然后關閉該文件。
打開文件和關閉文件就是一個結對的操作。
上下文管理器的常見用例:是資源的加鎖與解鎖,文件的打開與關閉。
上下文管理器
上下文管理器協議:是指類需要實現 __ enter __ 和 __ exit __ 方法。
就跟迭代器有迭代器協議一樣,迭代器協議需要實現 __ iter __ 和 __ next __ 方法。
上下文管理器,也就是支持上下文管理協議的對象,簡單點講就是,實現了 __ enter __ 和 __ exit __兩個方法的類。這個類也叫做,上下文管理器的類。
寫一個Open類,這個類是一個上下文管理器:
class Open:
def __init__(self, filepath, encoding):
self.filepath = filepath
self.encoding = encoding
def __enter__(self): # 當這個類被with關鍵字執行時,就自動調用這個方法。有返回值則調用給 as 聲明的變量
print('當這個類被with關鍵字執行時,就自動調用這個方法。有返回值則調用給 as 聲明的變量')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with 中代碼塊執行完就執行我這個函數')
with Open('1.txt', 'UTF-8') as f:
print('with 里面的代碼塊')
'''
結果:
當這個類被with關鍵字執行時,就自動調用這個方法。有返回值則調用給 as 聲明的變量
with 里面的代碼塊
with 中代碼塊執行完就執行我這個函數
'''
__ exit __(self, exc_type, exc_val, exc_tb):
里面的三個參數分別代表:異常類型,異常值,追溯信息。
注意:with語句中的代碼塊出現異常后,with后的代碼都無法執行
基於類的實現:完整實現Open方法
一個上下文管理器的類,起碼要定義 __ enter __ 和 __ exit __ 方法。
class Open:
def __init__(self, filepath, method):
self.file = open(filepath, method, encoding='utf-8')
def __enter__(self):
return self.file
def __exit__(self, type, value, traceback):
self.file.close()
with Open('1.txt', 'w') as f:
f.write('1111111111')
我們來看看底層發生了什么?
- with語句先暫存了 Open 類的 __ exit __ 方法
- 然后調用 Open 類的 __ enter __ 方法
- __ enter __ 方法打開文件並返回給with語句
- 打開的文件句柄傳遞給 as 后面的 f 參數
- 執行with里面的代碼塊。
- 調用之前暫存的 __ exit __ 方法
- 關閉文件
在第4步和第6步之間,如果發生異常,Python會將異常的type,value,traceback傳遞給 __ exit __ 方法。
當異常發生時,with語句會采取哪些步驟?
- with把異常的type, value, traceback 傳遞給 __ exit __ 方法
- with讓 __ exit __ 處理異常
- 如果 __ exit __ 返回的是True, 那么這個異常就被優雅的處理了。
- 如果 __ exit __ 返回的是True以外的任何東西,那個這個異常將被with 語句拋出。
當 __ exit __()返回值為True, 那么異常會被清空,就好像啥都沒發生一樣,with后的語句正常執行.。
完整模擬Open:
class Open:
def __init__(self, filepath, mode='r', encoding='utf-8'):
self.filepath = filepath
self.mode = mode
self.encoding = encoding
def __enter__(self):
self.file = open(self.filepath, mode=self.mode, encoding=self.encoding)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
self.file.close()
return True
with Open('1.txt', 'w', encoding='utf-8') as f:
f.write('哈哈哈')
f.werwer # 拋出異常,交給exit處理。后面的代碼正常運行
優點
- 使用with的語句的目的就是把代碼塊放入with中執行, with結束后,自動完成清理工作,無需干預。
- 在需要管理一些資源比如文件,網絡連接和鎖的編程環境中,可以在 __ exit __ 中定制自動釋放資源的機制。
基於生成器實現一個上下文管理器
contextlib模塊:可以使用一個生成器實現一個上下文管理器,而不是使用一個類。眾所周知,在類中還需要實現 __ enter __ 和 __ exit __ 。
from contextlib import contextmanager
@contextmanager
def point(x, y):
print('在yield之前')
yield x * y # yield出去的值賦給 as 后面的變量
print('在yield之后')
with point(3, 4) as p:
print('p',p)
'''
結果:
在yield之前
p 12
在yield之后
'''
利用contextlib模塊實現一個open
@contextmanager
def my_open(path):
f = open(path, mode='w')
yield f # 把這個f 賦給as后面的變量
f.close()
with my_open('2.txt') as f:
f.write('我是你爹')