操做文件對象時能夠:python
with open('a.txt') as f: '代碼塊'
上述叫作上下文管理協議,即with語句。編程
想象一下,你有兩個須要結對執行的相關操做,而後,還要在他們中間放置一段代碼。好比打開一個文件,操做文件,而後關閉該文件。網絡
打開文件和關閉文件就是一個結對的操做。函數
上下文管理器的常見用例:是資源的加鎖與解鎖,文件的打開與關閉。code
上下文管理器協議:是指類須要實現 __ enter __ 和 __ exit __ 方法。對象
就跟迭代器有迭代器協議同樣,迭代器協議須要實現 __ iter __ 和 __ next __ 方法。utf-8
上下文管理器,也就是支持上下文管理協議的對象,簡單點講就是,實現了 __ enter __ 和 __ exit __兩個方法的類。這個類也叫作,上下文管理器的類。資源
寫一個Open類,這個類是一個上下文管理器:it
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):class
裏面的三個參數分別表明:異常類型,異常值,追溯信息。
注意: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')
咱們來看看底層發生了什麼?
在第4步和第6步之間,若是發生異常,Python會將異常的type,value,traceback傳遞給 __ exit __ 方法。
當異常發生時,with語句會採起哪些步驟?
當 __ exit __()返回值爲True, 那麼異常會被清空,就好像啥都沒發生同樣,with後的語句正常執行.。
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處理。後面的代碼正常運行
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('我是你爹')