鍍金池/ 教程/ Python/ 異常
附錄
進(jìn)程通信
操作系統(tǒng)
迭代器
模塊
描述符
裝飾器
第三部分 擴(kuò)展庫
內(nèi)置類型
數(shù)據(jù)存儲
數(shù)據(jù)類型
基本環(huán)境
文件與目錄
異常
程序框架
數(shù)學(xué)運(yùn)算
函數(shù)
元類
字符串
表達(dá)式

異常

異常不僅僅是錯(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í)行。

  • enter: 初始化環(huán)境,返回上下文對象。
  • exit: 執(zhí)行清理操作。返回 True 時(shí),將阻止異常向外傳遞。
>>> 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í)行過程。

  • 通過 enter 調(diào)用 clsoing 函數(shù),將 yield 結(jié)果作為 enter 返回值。
  • yield 讓出了 closing 執(zhí)行權(quán)限,轉(zhuǎn)而執(zhí)行 with 代碼塊。
  • 執(zhí)行完畢,exit 發(fā)送消息,通知 yield 恢復(fù)執(zhí)行 closing 后續(xù)代碼。

和第 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é)議的用途很廣,比如:

  • Synchronized: 為代碼塊提供 lock/unlock 線程同步。
  • DBContext: 為代碼塊中的邏輯提供共享的數(shù)據(jù)庫連接,并負(fù)責(zé)關(guān)閉連接。
  • 等等……

如果你從沒拋出過自定義異常,那么得好好想想了……

上一篇:裝飾器下一篇:基本環(huán)境