異常不僅僅是錯(cuò)誤,還是一種正常的跳轉(zhuǎn)邏輯。
除多了個(gè)可選的 else 分支外,與其他語言并無多大差別。
>>> def test(n):
... try:
... if n % 2:
... raise Exception("Error Message")
... except Exception as ex:
... print "Exception:", ex.message
... else:
... print "Else..."
... finally:
... print "Finally..."
>>> test(1) # 引發(fā)異常,else 分支未執(zhí)行,finally 總是在最后執(zhí)行。
Exception: Error Message
Finally...
>>> test(2) # 未引發(fā)異常,else 分支執(zhí)行。
Else...
Finally...
關(guān)鍵字 raise 拋出異常,else 分支只在沒有異常發(fā)生時(shí)執(zhí)行。可無論如何,finally 總會被執(zhí)行。
可以有多個(gè) except 分支捕獲不同類型的異常。
>>> def test(n):
... try:
... if n == 0:
... raise NameError()
... elif n == 1:
... raise KeyError()
... elif n == 2:
... raise IndexError()
... else:
... raise Exception()
... except (IndexError, KeyError) as ex: # 可以同時(shí)捕獲不同類型的異常。
... print type(ex)
... except NameError: # 捕獲具體異常類型,但對異常對象沒興趣。
... print "NameError"
... except: # 捕獲任意類型異常。
... print "Exception"
>>> test(0)
NameError
>>> test(1)
<type 'exceptions.KeyError'>
>>> test(2)
<type 'exceptions.IndexError'>
>>> test(3)
Exception
下面這種寫法已經(jīng)被 Python 3 拋棄,不建議使用。
>>> def test():
... try:
... raise KeyError, "message" # 相當(dāng)于 KeyError("Message")
... except (IndexError, KeyError), ex: # 相當(dāng)于 as ex
... print type(ex)
支持在 except 中重新拋出異常。
>>> def test():
... try:
... raise Exception("error")
... except:
... print "catch exception"
... raise # 原樣拋出異常,不會修改 traceback 信息。
>>> test()
catch exception
Traceback (most recent call last):
raise Exception("error")
Exception: error
如果需要,可用 sys.exc_info() 獲取調(diào)用堆棧上的最后異常信息。
>>> def test():
... try:
... raise KeyError("key error")
... except:
... exc_type, exc_value, traceback = sys.exc_info()
... sys.excepthook(exc_type, exc_value, traceback) # 顯示異常信息
>>> test()
Traceback (most recent call last):
raise KeyError("key error")
KeyError: 'key error'
自定義異常通常繼承自 Exception。應(yīng)該用具體異常類型表示不同的錯(cuò)誤行為,而不是 message這樣的狀態(tài)值。
除了異常,還可以顯示警告信息。warnings 模塊另有函數(shù)用來控制警告的具體行為。
>>> import warnings
>>> def test():
... warnings.warn("hi") # 默認(rèn)僅顯式警告信息,不會中斷執(zhí)行。
... print "test..."
>>> test()
UserWarning: hi
test...
斷言 (assert) 雖然簡單,但遠(yuǎn)比用 print 輸出調(diào)試好得多。
>>> def test(n):
... assert n > 0, "n 必須大于 0" # 錯(cuò)誤信息是可選的。
... print n
>>> test(1)
1
>>> test(0)
Traceback (most recent call last):
assert n > 0, "n 必須大于 0"
AssertionError: n 必須大于 0
很簡單,當(dāng)條件不符時(shí),拋出 AssertionError 異常。assert 受只讀參數(shù) debug 控制,可以在啟動時(shí)添加 "-O" 參數(shù)使其失效。
$ python -O main.py
上下文管理協(xié)議 (Context Management Protocol) 為代碼塊提供了包含初始化和清理操作的安全上下文環(huán)境。即便代碼塊發(fā)生異常,清理操作也會被執(zhí)行。
>>> class MyContext(object):
... def __init__(self, *args):
... self._data = args
...
... def __enter__(self):
... print "__enter__"
... return self._data # 不一定要返回上下文對象自身。
...
... def __exit__(self, exc_type, exc_value, traceback):
... if exc_type: print "Exception:", exc_value
... print "__exit__"
... return True # 阻止異常向外傳遞。
>>> with MyContext(1, 2, 3) as data: # 將 __enter__ 返回的對象賦值給 data。
... print data
__enter__
(1, 2, 3)
__exit__
>>> with MyContext(1, 2, 3): # 發(fā)生異常,顯示并攔截。
... raise Exception("data error")
__enter__
Exception: data error
__exit__
可以在一個(gè) with 語句中使用多個(gè)上下文對象,依次按照 FILO 順序調(diào)用。
>>> class MyContext(object):
... def __init__(self, name):
... self._name = name
...
... def __enter__(self):
... print self._name, "__enter__"
... return self
...
... def __exit__(self, exc_type, exc_value, traceback):
... print self._name, "__exit__"
... return True
>>> with MyContext("a"), MyContext("b"):
... print "exec code..."
a __enter__
b __enter__
exec code...
b __exit__
a __exit__
contextlib
標(biāo)準(zhǔn)庫 contextlib 提供了一個(gè) contextmanager 裝飾器,用來簡化上下文類型開發(fā)。
>>> from contextlib import contextmanager
>>> @contextmanager
... def closing(o):
... print "__enter__"
... yield o
... print "__exit__"
... o.close() # 正常情況下要檢查很多條件,比如 None,是否有 close 方法等。
>>> with closing(open("README.md", "r")) as f:
... print f.readline()
__enter__
#學(xué)習(xí)筆記
__exit__
原理很簡單,contextmanager 替我們創(chuàng)建 Context 對象,并利用 yield 切換執(zhí)行過程。
和第 5 章提到的用 yield 改進(jìn)回調(diào)的做法差不多。contextmanager 讓我們少寫了很多代碼。但也有個(gè)麻煩,因?yàn)椴皇亲约簩?exit,所以得額外處理異常。
>>> @contextmanager
... def closing(o):
... try:
... yield o
... except:
... pass # 忽略,或拋出。
... finally: # 確保 close 被執(zhí)行。
... o.close()
contextlib 已有現(xiàn)成的 closing 可用,不用費(fèi)心完善上面的例子。
上下文管理協(xié)議的用途很廣,比如:
如果你從沒拋出過自定義異常,那么得好好想想了……