簡單介紹Python中的try和finally和with方法

來源:互聯網
上載者:User
用 Python 做一件很平常的事情: 開啟檔案, 逐行讀入, 最後關掉檔案; 進一步的需求是, 這也許是程式中一個可選的功能, 如果有任何問題, 比如檔案無法開啟, 或是讀取出錯, 那麼在函數內需要捕獲所有異常, 輸出一行警告並退出. 代碼可能一開始看起來是這樣的

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 塊外部, 如

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

那麼, 問題在於當開啟檔案失敗, 拋出異常將不會被捕獲.

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

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 結構來解決, 如

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!')

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

init enter  kon!  File "test.py", line 17, in    raise ValueError('kon!')  exit

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

簡單的介紹到此為止, 詳細的情況可以參考 PEP 343 (這數字真不錯, 7 3 ).

下面介紹下 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)

用 Python 做一件很平常的事情: 開啟檔案, 逐行讀入, 最後關掉檔案; 進一步的需求是, 這也許是程式中一個可選的功能, 如果有任何問題, 比如檔案無法開啟, 或是讀取出錯, 那麼在函數內需要捕獲所有異常, 輸出一行警告並退出. 代碼可能一開始看起來是這樣的

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 塊外部, 如

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

那麼, 問題在於當開啟檔案失敗, 拋出異常將不會被捕獲.

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

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 結構來解決, 如

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!')

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

init enter  kon!  File "test.py", line 17, in    raise ValueError('kon!')  exit

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

簡單的介紹到此為止, 詳細的情況可以參考 PEP 343 (這數字真不錯, 7 3 ).

下面介紹下 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)
  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.