理解Python中的“with”
1. 緣起
Python中,打開文件的操作是非常常見的,也是非常方便的,那么如何優雅的打開一個文件?大部分的同學會這樣實現:
with open( "a.txt" ) as f :
# do something
大家都知道,這樣寫可以自動處理資源的釋放、處理異常等,化簡了我們打開文件的操作,那么, with 到底做了什么呢?
從《Python學習手冊》中是這么描述的:
簡而言之,with/as語句的設計是作為常見try/finally用法模式的替代方案。就像try/finally語句,with/as語句也是用于定義必須執行的終止或“清理”行為,無論步驟中是否發生異常。不過,和try/finally不同的是,with語句支持更豐富的基于對象的協議,可以為代碼塊定義支持進入和離開動作。
也就是說對于代碼:
with expression [as varible]:
with-block
with語句的實際工作方式:
1.計算表達式,所得到的對象是 環境管理器 ,他必須有 enter , exit 兩個方法。
2.環境管理器的 enter 方法會被調用。如果as存在, enter 的返回值賦值給as后面的變量,否則,被丟棄。
3.代碼塊中嵌套的代碼(with-block)會執行。
4.如果with代碼塊會引發異常, exit (type,value,traceback)方法就會被調用。這些也是由sys.exec
info返回相同的值。如果此方法返回為假,則異常會重新引發。否則,異常會中止。正常情況下異常是應該被重新引發,這樣的話傳遞到with語句外。
5.如果with代碼塊沒有引發異常, _exit
方法依然會調用,其type、value以及traceback參數會以None傳遞。
with/as語句的設計,是為了讓必須在程序代碼塊周圍發生的啟動和終止活動一定會發生。和try/finally語句(無論異常是否發生其離開動作都會執行)類似,但是with/as有更豐富的對象協議,可以定義進入和離開的動作。
2. 設計的初衷
with/as語句的設計的初衷,在 PEP343 中是這么描述的:
This PEP adds a new statement “with” to the Python language to make it possible to factor out standard uses of try/finally statements.
In this PEP, context managers provide enter () and exit () methods that are invoked on entry to and exit from the body of the with statement.
對于下面的操作:
with EXPR as VAR:
BLOCK
等價于
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
# 將__enter__函數調用的返回值返回給VAR
VAR = value # Only if "as VAR" is present
# 執行BLOCK
BLOCK
except:
# 異常處理,The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# 清理,The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)
我們可以看到上述代碼完整的處理了初始化及異常/正常場景的清理操作,這便是 with 的設計思想,化簡了冗余的代碼,把那些重復的工作以及異常處理操作交給寫“EXPR”源碼(比如open操作)的同學。
3. 更深入的學習
我們繼續深入的看下 Python3 中 enter 和 exit 的實現:
class IOBase(metaclass=abc.ABCMeta):
# ... ...
### Context manager ###
def __enter__(self): # That's a forward reference
"""Context management protocol. Returns self (an instance of IOBase)."""
self._checkClosed()
return self
def __exit__(self, *args):
"""Context management protocol. Calls close()"""
self.close()
和我們預期的一致,在 enter 中返回了這個IO對象,然后在 exit 中,進行了清理。