在實際的編碼過程中,有時有一些任務,需要事先做一些設置,事后做一些清理,這時就需要python3 with出場了,with能夠對這樣的需求進行一個比較優雅的處理,最常用的例子就是對訪問文件的處理。
文件讀寫初級:
一般訪問文件資源時我們會這樣處理:
1 f = open(r'c:\mytest.txt', 'r') 2 data = f.read() 3 f.close()
存在兩個問題:
1. 如果在讀寫時出現異常而忘了異常處理。
2. 忘了關閉文件句柄
文件讀寫中級:
以下的加強版本的寫法:
1 f = open(r'c:\mytest.txt', 'r') 2 try: 3 data = f.read() 4 finally: 5 f.close()
以上的寫法就可以避免因讀取文件時異常的發生而沒有關閉問題的處理了。代碼長了一些。
文件讀寫高級:
使用with有更優雅的寫法:
1 with open(r'c:\test.txt', 'r') as f: 2 data = f.read()
說明:
with后面接的對象返回的結果賦值給f。此例當中open函數返回的文件對象賦值給了f;with會自已獲取上下文件的異常信息。
with語句的工作原理
__enter__()/__exit__()這兩個方法
with后面返回的對象要求必須有這兩個方法,而文件對象f剛好是有這兩個方法的。
object.__enter__(self)
進入與此對象相關的運行時上下文。with語句將將此方法的返回值綁定到語句的AS子句中指定的目標(如果有設置的話)
object.__exit__(self, exc_type, exc_value, traceback)
退出與此對象相關的運行時上下文。參數描述導致上下文退出的異常。如果上下文運行時沒有異常發生,那么三個參數都將置為None。
如果有異常發生,並且該方法希望抑制異常(即阻止它被傳播),則它應該返回True。否則,異常將在退出該方法時正常處理。
注意:
__exit__()方法不應該重新拋出傳入的異常,這是調用者的職責。
下面,以3個實例講解:
1、無異常情況:
1 class Test: 2 def __enter__(self): 3 print('__enter__() is call!') 4 return self 5 6 def dosomething(self): 7 print('dosomethong!') 8 9 def __exit__(self, exc_type, exc_value, traceback): 10 print('__exit__() is call!') 11 print(f'type:{exc_type}') 12 print(f'value:{exc_value}') 13 print(f'trace:{traceback}') 14 print('__exit()__ is call!') 15 16 with Test() as sample: 17 sample.dosomething() 18 19 20 >>>__enter__() is call! 21 >>>dosomethong! 22 >>>__exit__() is call! 23 >>>type:None 24 >>>value:None 25 >>>trace:None 26 >>>__exit()__ is call!
以上的實例Text,我們注意到他帶有__enter__()/__exit__()這兩個方法,當對象被實例化時,就會主動調用__enter__()方法,任務執行完成后就會調用__exit__()方法,另外,注意到,__exit__()方法是帶有三個參數的(exc_type, exc_value, traceback), 依據上面的官方說明:如果上下文運行時沒有異常發生,那么三個參數都將置為None, 這里三個參數由於沒有發生異常,的確是置為了None, 與預期一致。
2、出現並拋出異常:
1 class Test: 2 def __enter__(self): 3 print('__enter__() is call!') 4 return self 5 6 def dosomething(self): 7 x = 1/0 8 print('dosomethong!') 9 10 def __exit__(self, exc_type, exc_value, traceback): 11 print('__exit__() is call!') 12 print(f'type:{exc_type}') 13 print(f'value:{exc_value}') 14 print(f'trace:{traceback}') 15 print('__exit()__ is call!') 16 # return True 17 18 19 with Test() as sample: 20 sample.dosomething() 21 >>> 22 __enter__() is call! 23 Traceback (most recent call last): 24 __exit__() is call! 25 type:<class 'ZeroDivisionError'> 26 File "C:/Users/xxx/PycharmProjects/Test1/test.py", line 23, in <module> 27 value:division by zero 28 sample.dosomething() 29 trace:<traceback object at 0x000001C08CF32F88> 30 File "C:/Users/xxx/PycharmProjects/Test1/test.py", line 10, in dosomething 31 __exit()__ is call! 32 x = 1/0 33 ZeroDivisionError: division by zero
從結果可以看出, 在執行到dosomethong時就發生了異常,然后將異常傳給了__exit__(), 依據上面的官方說明:如果有異常發生,並且該方法希望抑制異常(即阻止它被傳播),則它應該返回True。否則,異常將在退出該方法時正常處理。當前__exit__並沒有寫明返回True,故會拋出異常,也是合理的,但是正常來講,程序應該是不希望它拋出異常的,這也是調用者的職責,我們將再次修改__exit__, 將其返回設置為True,
3、出現異常,阻止異常拋出:
1 class Test: 2 def __enter__(self): 3 print('__enter__() is call!') 4 return self 5 6 def dosomething(self): 7 x = 1/0 8 print('dosomethong!') 9 10 def __exit__(self, exc_type, exc_value, traceback): 11 print('__exit__() is call!') 12 print(f'type:{exc_type}') 13 print(f'value:{exc_value}') 14 print(f'trace:{traceback}') 15 print('__exit()__ is call!') 16 return True 17 18 19 with Test() as sample: 20 sample.dosomething() 21 22 >>> 23 __enter__() is call! 24 __exit__() is call! 25 type:<class 'ZeroDivisionError'> 26 value:division by zero 27 trace:<traceback object at 0x000001C94E592F88> 28 __exit()__ is call!
從結果看,異常拋出被抑制了,符合預期。
---------------------
本文為CSDN博主「五力」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lxy210781/article/details/81176687