首發時間:2018-02-23 15:28python
以前看到一篇博客說博主python面試時遇到面試官提問with的原理,而那位博主的博文沒有說起with原理,故有此文。
關於with語句,官方文檔中是這樣描述的:
The with
statement is used to wrap the execution of a block with methods defined by a context manager (see section With Statement Context Managers). This allows common try
...except
...finally
usage patterns to be encapsulated for convenient reuse. 面試
with_stmt ::= "with" with_item ("," with_item)* ":"
suite
數據庫with_item ::=
expression
["as"target
] express
The execution of the with statement with one 「item」 proceeds as follows:測試
The context expression (the expression given in the with_item) is evaluated to obtain a context manager.ui
The context manager’s __exit__() is loaded for later use.this
The context manager’s __enter__() method is invoked.lua
If a target was included in the with statement, the return value from __enter__() is assigned to it.spa
Note
The with statement guarantees that if the __enter__() method returns without an error, then __exit__() will always be called. Thus, if an error occurs during the assignment to the target list, it will be treated the same as an error occurring within the suite would be. See step 6 below.翻譯
The suite is executed.
The context manager’s __exit__() method is invoked. If an exception caused the suite to be exited, its type, value, and traceback are passed as arguments to __exit__(). Otherwise, three None arguments are supplied.
谷歌翻譯成中文就是:
with語句用於使用由上下文管理器定義的方法來封裝塊的執行(請參見使用語句上下文管理器一節)。 這容許通用的try…except…finally使用模式被封裝以便於重用【這句話大概意思就是「with語句」相似於try…except…finally封裝以後的的狀況】。
帶有一個「項目」的with語句的執行過程以下:
1.上下文表達式(在with_item中給出的表達式)被評估以得到上下文管理器。【會區分類型來處理,如文件,進程等均可以使用with語句】
2.上下文管理器的__exit __()被加載供之後使用。【負責上下文的退出】
3.上下文管理器的__enter __()方法被調用。【負責上下文的進入】
4.若是在with語句中包含目標,則將__enter __()的返回值分配給它。【若是with後面跟着as 對象(如with open() as f),那麼此對象得到with上下文對象的__enter__()的返回值,(附:應該是相似操做數據庫時的鏈接對象和遊標的區別)】
注意
with語句保證,若是__enter __()方法返回時沒有錯誤,那麼將始終調用__exit __()。 所以,若是在分配給目標列表期間發生錯誤,它將被視爲與套件內發生的錯誤相同。 請參閱下面的第6步。
5.該套件已執行。【意思就是語句體中的過程執行完畢,執行完畢就到第六步--調用__exit__()來退出】
6.上下文管理器的__exit __()方法被調用。 若是異常致使套件退出,則其類型,值和回溯做爲參數傳遞給__exit __()。 不然,將提供三個無參數。
關於退出返回值: If the suite was exited due to an exception, and the return value from the __exit__() method was false, the exception is reraised. If the return value was true, the exception is suppressed, and execution continues with the statement following the with statement. If the suite was exited for any reason other than an exception, the return value from __exit__() is ignored, and execution proceeds at the normal location for the kind of exit that was taken. 中文: 若是套件因爲異常而退出,而且__exit __()方法的返回值爲false,則會從新對異常進行從新評估。 若是返回值爲true,則異常被抑制,並繼續執行with語句後面的語句。 若是套件因爲除了異常以外的任何緣由而退出,則__exit __()的返回值將被忽略,而且執行將在正常位置繼續進行。
意思就是:
若是是異常退出,那麼會返回false,(根據文檔中的exit的描述「that methods should not reraise the passed-in exception; this is the caller’s responsibility.」,大概意思就是exit()不會處理異常,會從新拋出異常拋出給外面,由調用者處理,由於這是調用者的責任)__exit__()
若是返回 True,則忽略異常,再也不對異常進行處理【(在exit內部處理完異常後,可讓」__exit__()」方法返回True,此時該異常就會不會再被拋出,with會認爲它的執行體沒有發生異常)】
(with會識別返回值,根據返回值來處理,若是是False,那麼with會將執行體中的異常拋出,若是是True,那麼with會認爲沒有發生異常(忽略異常),而繼續執行外面的語句,但因爲內部調用的了__exit__(),因此在異常以後的語句是不會運行的)
附上一個文檔中提供的一個關於with中使用鎖的例子:
幾個測試:
1.執行體中發生異常:
import time class myContextDemo(object): def __init__(self,gen): self.gen = gen def __enter__(self): print("enter in ") return self.gen def __exit__(self, exc_type, exc_val, exc_tb): #exc_type是exception_type exc_val是exception_value exc_tb是exception_trackback print("exit in ") if exc_type is None:#若是是None 則繼續執行 print("None:",exc_type, exc_val, exc_tb) else: #異常不爲空時執行,這一步,若是with語句體中發生異常,那麼也會執行 print("exception:", exc_type, exc_val, exc_tb) print("all done") if __name__=="__main__": gen=(i for i in range(5,10)) G=myContextDemo(gen) with G as f : print("hello") for i in f: print(i,end="\t") #測試1:執行體中發生異常 raise Exception("母雞啊") print("main continue")
1.拋出異常後,後面main continue再也不執行
2.__exit__()中的else會執行
測試2:當else中強制返回爲True時:
import time class myContextDemo(object): def __init__(self,gen): self.gen = gen def __enter__(self): print("enter in ") return self.gen def __exit__(self, exc_type, exc_val, exc_tb): #exc_type是exception_type exc_val是exception_value exc_tb是exception_trackback print("exit in ") if exc_type is None:#若是是None 則繼續執行 print("None:",exc_type, exc_val, exc_tb) else: #異常不爲空時執行,這一步,若是with語句體中發生異常,那麼也會執行 print("exception:", exc_type, exc_val, exc_tb) print("all done") return True #這裏若是返回true能夠看到發生異常後,main continue能夠執行 #即,若是exc_type是true,那麼會繼續執行,實際上,也能夠在這裏處理一下異常再返回true if __name__=="__main__": gen=(i for i in range(5,10)) G=myContextDemo(gen) with G as f : print("hello") for i in f: print(i,end="\t") raise Exception("母雞啊") # print("continue")#這裏不會執行 print("main continue")
1.返回True以後,with會忽略異常,繼續執行,因此這裏「main continue」能執行
2.即便忽略異常,在with體中異常以後的語句依舊不會執行
附:理論上能夠在返回True以前處理一下異常
PS:若是你們想要了解得更詳細,能夠本身嘗試去讀一下官方文檔。
附上關於with語句的詳細介紹官方文檔:https://www.python.org/dev/peps/pep-0343/