在使用Python編程中,可以會經常碰到這種情況:有一個特殊的語句塊,在執行這個語句塊之前需要先執行一些准備動作;當語句塊執行完成後,需要繼續執行一些收尾動作。
例如:當需要操作文件或數據庫的時候,首先需要獲取文件句柄或者數據庫連接對象,當執行完相應的操作後,需要執行釋放文件句柄或者關閉數據庫連接的動作。
又如,當多線程程序需要訪問臨界資源的時候,線程首先需要獲取互斥鎖,當執行完成並准備退出臨界區的時候,需要釋放互斥鎖。
對於這些情況,Python中提供了上下文管理器(Context Manager)的概念,可以通過上下文管理器來定義/控制代碼塊執行前的准備動作,以及執行後的收尾動作。
那麼在Python中怎麼實現一個上下文管理器呢?這裡,又要提到兩個"魔術方法",__enter__和__exit__,下面就是關於這兩個方法的具體介紹。
也就是說,當我們需要創建一個上下文管理器類型的時候,就需要實現__enter__和__exit__方法,這對方法就稱為上下文管理協議(Context Manager Protocol),定義了一種運行時上下文環境。
在Python中,可以通過with語句來方便的使用上下文管理器,with語句可以在代碼塊運行前進入一個運行時上下文(執行__enter__方法),並在代碼塊結束後退出該上下文(執行__exit__方法)。
with語句的語法如下:
with context_expr [as var]: with_suite
在Python的內置類型中,很多類型都是支持上下文管理協議的,例如file,thread.LockType,threading.Lock等等。這裡我們就以file類型為例,看看with語句的使用。
當需要寫一個文件的時候,一般都會通過下面的方式。代碼中使用了try-finally語句塊,即使出現異常,也能保證關閉文件句柄。
logger = open("log.txt", "w") try: logger.write('Hello ') logger.write('World') finally: logger.close() print logger.closed
其實,Python的內置file類型是支持上下文管理協議的,可以直接通過內建函數dir()來查看file支持的方法和屬性:
>>> print dir(file) ['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', ' __getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclass hook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', ' mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines'] >>>
所以,可以通過with語句來簡化上面的代碼,代碼的效果是一樣的,但是使用with語句的代碼更加的簡潔:
with open("log.txt", "w") as logger: logger.write('Hello ') logger.write('World') print logger.closed
對於自定義的類型,可以通過實現__enter__和__exit__方法來實現上下文管理器。
看下面的代碼,代碼中定義了一個MyTimer類型,這個上下文管理器可以實現代碼塊的計時功能:
import time class MyTimer(object): def __init__(self, verbose = False): self.verbose = verbose def __enter__(self): self.start = time.time() return self def __exit__(self, *unused): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 if self.verbose: print "elapsed time: %f ms" %self.msecs
下面結合with語句使用這個上下文管理器:
def fib(n): if n in [1, 2]: return 1 else: return fib(n-1) + fib(n-2) with MyTimer(True): print fib(30)
代碼輸出結果為:
在使用上下文管理器中,如果代碼塊 (with_suite)產生了異常,__exit__方法將被調用,而__exit__方法又會有不同的異常處理方式。
當__exit__方法退出當前運行時上下文時,會並返回一個布爾值,該布爾值表明了"如果代碼塊 (with_suite)執行中產生了異常,該異常是否須要被忽略"。
1. __exit__返回False,重新拋出(re-raised)異常到上層
修改前面的例子,在MyTimer類型中加入了一個參數"ignoreException"來表示上下文管理器是否會忽略代碼塊 (with_suite)中產生的異常。
import time class MyTimer(object): def __init__(self, verbose = False, ignoreException = False): self.verbose = verbose self.ignoreException = ignoreException def __enter__(self): self.start = time.time() return self def __exit__(self, *unused): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 if self.verbose: print "elapsed time: %f ms" %self.msecs return self.ignoreException try: with MyTimer(True, False): raise Exception("Ex4Test") except Exception, e: print "Exception (%s) was caught" %e else: print "No Exception happened"
運行這段代碼,會得到以下結果,由於__exit__方法返回False,所以代碼塊 (with_suite)中的異常會被繼續拋到上層代碼。
2. __exit__返回Ture,代碼塊 (with_suite)中的異常被忽略
將代碼改為__exit__返回為True的情況:
try: with MyTimer(True, True): raise Exception("Ex4Test") except Exception, e: print "Exception (%s) was caught" %e else: print "No Exception happened"
運行結果就變成下面的情況,代碼塊 (with_suite)中的異常被忽略了,代碼繼續運行:
一定要小心使用__exit__返回Ture的情況,除非很清楚為什麼這麼做。
3. 通過__exit__函數完整的簽名獲取更多異常信息
對於__exit__函數,它的完整簽名如下,也就是說通過這個函數可以獲得更多異常相關的信息。
繼續修改上面例子中的__exit__函數如下:
def __exit__(self, exception_type, exception_value, traceback): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 if self.verbose: print "elapsed time: %f ms" %self.msecs print "exception_type: ", exception_type print "exception_value: ", exception_value print "traceback: ", traceback return self.ignoreException
這次運行結果中,就顯示出了更多異常相關的信息了:
本文介紹了Python中的上下文管理器,以及如何結合with語句來使用上下文管理器。
總結一下with 語句的執行流程:
在很多情況下,with語句可以簡化代碼,並增加代碼的健壯性。
無需操作系統直接運行 Python 代碼 http://www.linuxidc.com/Linux/2015-05/117357.htm
CentOS上源碼安裝Python3.4 http://www.linuxidc.com/Linux/2015-01/111870.htm
《Python核心編程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm
《Python開發技術詳解》.( 周偉,宗傑).[高清PDF掃描版+隨書視頻+代碼] http://www.linuxidc.com/Linux/2013-11/92693.htm
Python腳本獲取Linux系統信息 http://www.linuxidc.com/Linux/2013-08/88531.htm
在Ubuntu下用Python搭建桌面算法交易研究環境 http://www.linuxidc.com/Linux/2013-11/92534.htm
Python 語言的發展簡史 http://www.linuxidc.com/Linux/2014-09/107206.htm
Python 的詳細介紹:請點這裡
Python 的下載地址:請點這裡