原文鏈接:
https://blog.csdn.net/qiqicos/article/details/79200089
https://blog.csdn.net/elevenqiao/article/details/6796653
with…as,就是個python控制流語句,像 if ,while一樣。
with…as語句是簡化版的try except finally語句。
那我們先理解一下try…except…finally語句是干啥的。實際上,try…except語句和try…finally語句是兩種語句,用於不同的場景。但是當二者結合在一起時,可以“實現穩定性和靈活性更好的設計”。
1. try…except語句
用於處理程序執行過程中的異常情況,比如語法錯誤、從未定義變量上取值等等,也就是一些python程序本身引發的異常、報錯。比如你在python下面輸入 1 / 0:
1 >>> 1/0 2 Traceback (most recent call last): 3 File "<stdin>", line 1, in <module>
4 ZeroDivisionError: division by zero
系統會給你一個ZeroDivisionError的報錯。說白了就是為了防止一些報錯影響你的程序繼續運行,就用try語句把它們抓出來(捕獲)。
try…except的標准格式:
1 try: 2 ## normal block 3 except A: 4 ## exc A block 5 except: 6 ## exc other block 7 else: 8 ## noError block
程序執行流程是:
1 –>執行normal block 2 –>發現有A錯誤,執行 exc A block(即處理異常) 3 –>結束 4 如果沒有A錯誤呢? 5 –>執行normal block 6 –>發現B錯誤,開始尋找匹配B的異常處理方法,發現A,跳過,發現except others(即except:),執行exc other block 7 –>結束 8 如果沒有錯誤呢? 9 –>執行normal block 10 –>全程沒有錯誤,跳入else 執行noError block 11 –>結束
Tips: 我們發現,一旦跳入了某條except語句,就會執行相應的異常處理方法(block),執行完畢就會結束。不會再返回try的normal block繼續執行了。
1 try: 2 a = 1 / 2 #a normal number/variable 3 print(a) 4 b = 1 / 0 # an abnormal number/variable 5 print(b) 6 c = 2 / 1 # a normal number/variable 7 print(c) 8 except: 9 print("Error")
輸出:
1 0.5 2 Error
結果是,先打出了一個0,又打出了一個Error。就是把ZeroDivisionError錯誤捕獲了。
先執行try后面這一堆語句,由上至下:
step1: a 正常,打印a. 於是打印出0.5 (python3.x以后都輸出浮點數)
step2: b, 不正常了,0 不能做除數,所以這是一個錯誤。直接跳到except報錯去。於是打印了Error。
step3: 其實沒有step3,因為程序結束了。c是在錯誤發生之后的b語句后才出現,根本輪不到執行它。也就看不到打印出的c了
但這還不是try/except的所有用法
except后面還能跟表達式的! 所謂的表達式,就是錯誤的定義。也就是說,我們可以捕捉一些我們想要捕捉的異常。而不是什么異常都報出來。
異常分為兩類:
- python標准異常
- 自定義異常
我們先拋開自定義異常(因為涉及到類的概念),看看except都能捕捉到哪些python標准異常。
1 try: 2 a = 1 / 2 3 print(a) 4 print(m) # 此處拋出python標准異常 5 b = 1 / 0 # 此后的語句不執行 6 print(b) 7 c = 2 / 1 8 print(c) 9 except NameError: 10 print("Ops!!") 11 except ZeroDivisionError: 12 print("Wrong math!!") 13 except: 14 print("Error")
輸出:
1 0.5 2 Ops!!
當程序執行到print(m)
的時候 發現了一個NameError: name 'm' is not defined
,於是控制流去尋找匹配的except異常處理語句。發現了第一條匹配,執行對應block。執行完結束。
2. try…finallly語句
用於無論執行過程中有沒有異常,都要執行清場工作。
好的 現在我們看看他倆合在一起怎么用!!
1 try: 2 execution block ##正常執行模塊 3 except A: 4 exc A block ##發生A錯誤時執行 5 except B: 6 exc B block ##發生B錯誤時執行 7 except: 8 other block ##發生除了A,B錯誤以外的其他錯誤時執行 9 else: 10 if no exception, jump to here ##沒有錯誤時執行 11 finally: 12 final block ##總是執行
tips: 注意順序不能亂,否則會有語法錯誤。如果用else就必須有except,否則會有語法錯誤。
1 try: 2 a = 1 / 2 3 print(a) 4 print(m) # 拋出NameError異常 5 b = 1 / 0 6 print(b) 7 c = 2 / 1 8 print(c) 9 except NameError: 10 print("Ops!!") # 捕獲到異常 11 except ZeroDivisionError: 12 print("Wrong math!!") 13 except: 14 print("Error") 15 else: 16 print("No error! yeah!") 17 finally: # 是否異常都執行該代碼塊 18 print("Successfully!")
輸出:
1 0.5 2 Ops!! 3 Successfully!
try語句終於搞清楚了! 那么可以繼續with…as的探險了
3. with…as語句
with as 語句的結構如下:
1 with expression [as variable]: 2 with-block
看這個結構我們可以獲取至少兩點信息 1. as可以省略 2. 有一個句塊要執行。也就是說with是一個控制流語句,跟if/for/while/try之類的是一類的,with可以用來簡化try finally代碼,看起來可以比try finally更清晰。這里新引入了一個"上下文管理協議"context management protocol,實現方法是為一個類定義__enter__和__exit__兩個函數。with expresion as variable的執行過程是,首先執行__enter__函數,它的返回值會賦給as后面的variable,想讓它返回什么就返回什么,只要你知道怎么處理就可以了,如果不寫as variable,返回值會被忽略。
然后,開始執行with-block中的語句,不論成功失敗(比如發生異常、錯誤,設置sys.exit()),在with-block執行完成后,會執行__exit__函數。這樣的過程其實等價於:
1 try: 2 執行 __enter__的內容 3 執行 with_block. 4 finally: 5 執行 __exit__內容
只不過,現在把一部分代碼封裝成了__enter__函數,清理代碼封裝成__exit__函數。
所謂上下文管理協議,其實是指with后面跟的expression。這個expression一般都是一個類的實體。這個類的實體里面要包含有對__enter__和__exit__函數的定義才行。
除了給類定義一些屬性之外,還可以定義類的方法。也就是允許對類的內容有哪些操作,最直觀的方法就是用dir()函數來看一個類的屬性和方法。比如要查看字符串類有哪些屬性和方法:
1 >>> dir(str) 2 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
類,除了python內置的,當然還可以自己定義! 所以除了expression表示一些含有這兩種方法的內置類,還可以自己定義類,讓他們含有enter和exit方法。
可能大家一直奇怪,為什么有的方法,比如上圖中的'split' 'title'什么的都沒有下划線,而又有很多,,比如__enter__和__exit__要寫下划線呢?請參考博文http://blog.csdn.net/qiqicos/article/details/79208039
那么__enter__
和__exit__
是怎么用的方法呢?我們直接來看一個例子好了。
1 class Sample(object): # object類是所有類最終都會繼承的類 2 def __enter__(self): # 類中函數第一個參數始終是self,表示創建的實例本身 3 print("In __enter__()") 4 return "Foo" 5 6 def __exit__(self, type, value, trace): 7 print("In __exit__()") 8 9 10 def get_sample(): 11 return Sample() 12 13 14 with get_sample() as sample: 15 print("sample:", sample) 16 17 18 print(Sample) # 這個表示類本身 <class '__main__.Sample'> 19 print(Sample()) # 這表示創建了一個匿名實例對象 <__main__.Sample object at 0x00000259369CF550>
輸出結果:
1 In __enter__() 2 sample: Foo 3 In __exit__() 4 <class '__main__.Sample'> 5 <__main__.Sample object at 0x00000226EC5AF550>
步驟分析:
–> 調用get_sample()函數,返回Sample類的實例;
–> 執行Sample類中的__enter__()方法,打印"In__enter_()"字符串,並將字符串“Foo”賦值給as后面的sample變量;
–> 執行with-block碼塊,即打印"sample: %s"字符串,結果為"sample: Foo"
–> 執行with-block碼塊結束,返回Sample類,執行類方法__exit__()。因為在執行with-block碼塊時並沒有錯誤返回,所以type,value,trace這三個arguments都沒有值。直接打印"In__exit__()"
程序有錯的例子:
1 class Sample: 2 def __enter__(self): 3 return self 4 5 def __exit__(self, type, value, trace): 6 print("type:", type) 7 print("value:", value) 8 print("trace:", trace) 9 10 def do_something(self): 11 bar = 1 / 0 12 return bar + 10 13 14 15 with Sample() as sample: 16 sample.do_something()
輸出結果:
1 type: <class 'ZeroDivisionError'> 2 value: division by zero 3 trace: <traceback object at 0x0000019B73153848> 4 Traceback (most recent call last): 5 File "F:/機器學習/生物信息學/Code/first/hir.py", line 16, in <module> 6 sample.do_something() 7 File "F:/機器學習/生物信息學/Code/first/hir.py", line 11, in do_something 8 bar = 1 / 0 9 ZeroDivisionError: division by zero
步驟分析:
–> 實例化Sample類,執行類方法__enter__(),返回值self也就是實例自己賦值給sample。即sample是Sample的一個實例(對象);
–>執行with-block碼塊: 實例sample調用方法do_something();
–>執行do_something()第一行 bar = 1 / 0,發現ZeroDivisionError,直接結束with-block代碼塊運行
–>執行類方法__exit__(),帶入ZeroDivisionError的錯誤信息值,也就是type,value, trace,並打印它們。