Python: try finally 與 上下文管理器簡介

    用 Python 作一件很日常的事情: 打開文件, 逐行讀入, 最後關掉文件; 進一步的需求是, 這也許是程序中一個可選的功能, 若是有任何問題, 好比文件沒法打開, 或是讀取出錯, 那麼在函數內須要捕獲全部異常, 輸出一行警告並退出. 代碼可能一開始看起來是這樣的
html

def read_file(): 
    try: 
        f = open('yui', 'r') 
        print ''.join(f.readlines()) 
    except: 
        print 'error occurs while reading file' 
    finally: 
        f.close()

    不過這顯然沒法運做, 由於  f  是在  try  塊中定義的, 而在  finally  中沒法引用.

    若是將  f  提取到  try  塊外部, 如
python

def read_file(): 
     f = open('azusa', 'r') 
    try: 
        print ''.join(f.readlines()) 
    except: 
        print 'error occurs while reading file' 
    finally: 
        f.close()

那麼, 問題在於當打開文件失敗, 拋出異常將不會被捕獲.

    挫一點的方法天然是, 再套一層  try  吧
express

def read_file(): 
     try: 
        f = open('sawako', 'r') 
        try: 
            print ''.join(f.readlines()) 
        except: 
            print 'error occurs while reading file' 
        finally: 
            f.close() 
     except: 
         print 'error occurs while reading file'

    固然這不單單是多一層縮進挫了, 連警告輸出都白白多一次呢.

    正規一點的方式是, 使用 Python 引入的  with  結構來解決, 如
app

def readFile(): 
    try: 
         with open('mio', 'r')  as f: 
            print ''.join(f.readlines()) 
    except: 
        print 'error occurs while reading file'

    當文件打開失敗時, 異常天然會被  except  到; 不然, 在  with  塊結束以後, 打開的文件將自動關閉.

    除了打開文件, 還有其它這樣能夠用於  with  的東西麼? 或者說, 怎麼自定義一個什麼東西, 讓它能用於  with 呢?
    直接回答後一個問題吧, 祕密在於 Python 虛擬機在  with  塊退出時會去尋找對象的  __exit__  方法並調用它, 把釋放資源的動做放在這個  __exit__  函數中就能夠了; 另外, 對象還須要一個  __enter__  函數, 當進入  with 塊時, 這個函數被調用, 而它的返回值將做爲  as  後引用的值. 一個簡單的例子是
函數

class Test: 
    def __init__(self): 
        print 'init' 

    def __enter__(self): 
        print 'enter' 
        return self 

    def __exit__(self, except_type, except_obj, tb): 
        print except_type 
        print except_obj 
        import traceback 
        print ''.join(traceback.format_tb(tb)) 
        print 'exit' 
        return True 

with Test() as t: 
    raise ValueError('kon!')

    執行這一段代碼, 輸出將會是
ui

init 
enter 
<type 'exceptions.ValueError'> 
kon! 
  File "test.py", line 17, in <module> 
    raise ValueError('kon!') 

exit

     __exit__  函數接受三個參數, 分別是異常對象類型, 異常對象和調用棧. 若是  with  塊正常退出, 那麼這些參數將都是  None . 返回  True  表示發生的異常已被處理, 再也不繼續向外拋出.

url

    簡單的介紹到此爲止, 詳細的狀況能夠參考  PEP 343  (這數字真不錯, 7 3 ). spa

下面介紹下 with 語句的實例用法 & 高級用法: 線程

Python高端、大氣、上檔次的with語句 設計

在說with語句以前,先看看一段簡單的代碼吧

lock = threading.Lock()
...
lock.acquire()
elem = heapq.heappop(heap)
lock.release()

很簡單直觀,多個線程共用一個優先級隊列的時候,首先先用互斥鎖lock.acquire()把優先級隊列鎖上,而後取元素,再而後lock.release()釋放這個鎖。

雖然看似很是符合邏輯的一個過程,可是裏面隱藏着一個巨大的bug:當heap裏面沒有元素的時候,會拋出一個IndexError異常,再而後堆棧回滾,再而後lock.release()根本不會執行,這個鎖就永遠得不到釋放,所以就發生了喜聞樂見的死鎖問題。這個也是不少大神們討厭異常的緣由。經典Java風格的解決方案就是

lock = threading.Lock()
...
lock.acquire()
try:
    elem = heapq.heappop(heap)
finally:
    lock.release()

這個雖然能夠,可是怎麼看怎麼dirty,和Python優雅、簡單的風格出入很大。其實,自從Python2.5開始引入了with語句,一切就變得很是簡單:

lock = threading.Lock()
...
with lock:
    elem = heapq.heappop(heap)

在此不管以何種方式離開with語句的代碼塊,鎖都會被釋放。
with語句的設計目的就是爲了使得以前須要經過try...finally解決的清理資源問題變得簡單、清晰,它的的用法是

with expression [as variable]:
    with-block

其中expression返回一個叫作「context manager」的對象,而後這個對象被賦給variable(若是有的話)。「context manager」對象有兩個方法,分別是__enter__()和__exit__(),很明顯一個在進入with-block時調用,一個離開with-block的時候調用。

這樣的對象不須要本身去實現,在Python標準庫裏面不少API都是已經實現了這兩個方法,最多見的一個例子就是讀寫文件的open語句。

with open('1.txt', encoding = 'utf-8') as fp:
    lines = fp.readlines()

不管是正常離開仍是由於異常緣由離開with語句塊,打開的文件資源老是會釋放。
接下去討論一下with語句配合contextlib庫的一些比較實用的方法,好比須要同時打開兩個文件,一個讀一個寫,這個時候就能夠這樣寫:

from contextlib import nested
...
with nested(open('in.txt'), open('out.txt', 'w')) as (fp_in, fp_out):
    ...

這樣就能夠省掉兩個with的語句的嵌套了,另外若是遇到一些尚未支持「context manager」的API呢?好比urllib.request.urlopen(),這個返回的對象由於不是「context manager」,結束的時候還須要本身去調用close方法。
相似這種API,contextlib提供了一個叫作closing方法,它會在離開with語句的時候,自動調用對象的close方法,所以urlopen也能夠這樣寫:

from contextlib import closing
...
with closing(urllib.request.urlopen('http://www.yahoo.com')) as f:
    for line in f:
        sys.stdout.write(line)


參考資料:

[1] 8 PEP 343: The 'with' statement

https://docs.python.org/release/2.5/whatsnew/pep-343.html

Refer:http://ling0322.info/2013/10/03/python-with-statement.html

[2] Python 上下文管理器

http://python.jobbole.com/82289/

相關文章
相關標籤/搜索