1. 程式人生 > 其它 >Python 中 with...as的用法

Python 中 with...as的用法


原文連結:

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,並列印它們。