Python語法速查: 10. 異常

返回目錄

 

本篇索引

(1)內置異常

(2)自定義異常

(3)主動引發異常

(4)捕捉異常

(5)error模塊

(6)with語句

 

 

 (1)內置異常

異常的基類:

以下這些異常作爲具體異常的基類,都不會被顯式引發,但是可以使用它們捕捉某種錯誤。

基類名稱 說明
BaseException

所有內置異常的基類,其他所有內置異常都派生自該類。

  Exception 所有內置的非系統退出異常都派生自此類(即除了:SystemExit, GeneratorExit, KeyboardInterrupt之外的所有內置異常)。所有用戶自定義異常也應當派生自此類。
  ArithmeticError 算術運算異常的基類,包括溢出、除零等等。
  LookupError 索引和鍵錯誤的基類。
  BufferError 當與緩衝區相關的操作無法執行時引發,一般由用戶自行繼承使用。

 

具體異常

以下異常屬於經常被引發的異常。

具體異常名稱 說明
以下異常直接繼承自:BaseException
GeneratorExit 由生成器的.close()方法引發。
KeyboardInterrupt 由鍵盤中斷(通常爲 Ctrl-C)生成。
SystemExit 程序退出,一般系統函數sys.exit()引發。
以下異常繼承自:BaseException -> Exception
StopIteration 引發後可停止迭代。
StopAsyncIteration 由異步迭代器引發停止迭代。
AssertionError 當 assert 語句失敗時引發。
AttributeError 當屬性引用或屬性賦值失敗時引發。
EOFError 當 input() 函數未讀取任何數據即達到文件結束條件 (EOF) 時將被引發。(文件操作的諸如read()和readline()方法等I/O操作在遇到EOF時會返回空字符串,而不是引發異常)
ImportError 當 import 語句無法找到模塊或者 from 無法在模塊中找到名稱時引發。Python3.3版本後,對此異常添加了name和path屬性,用來表示“嘗試導入的模塊名稱”和“觸發異常的文件所在的路徑”。
    ModuleNotFoundError ImportError的子類,Python3.6版本新加入。當一個模塊無法找到時由import引發。
MemoryError 可用內存不足(但仍可挽救時)時將被引發。
NameError 在局部或全局命名空間中未找到某個名稱時引發。
    UnboundLocalError NameError的子類。引用了未綁定的局部變量時引發。
OSError Python3.3起,以下都是OSError的別名:IOError、EnvironmentError、WindowsError(僅限Windows中)。
操作系統錯誤,主要由os模塊中的函數引起。
    BlockingIOError OSError的子類。當一個操作阻塞一個設置爲非阻塞操作的對象時引發。
    ChildProcessError OSError的子類。當對子進程進行操作失敗時將引發,對應於Linux系統調用時的errno中的 ECHILD
    ConnectionError 與連接相關問題的基類。
        BrokenPipeError ConnectionError的子類。當試圖寫入另一端已被關閉的管道,或是試圖寫入已關閉寫入的套接字時引發。對應於Linux系統調用時的errno中的 EPIPE 和 ESHUTDOWN。
        ConnectionAbortedError ConnectionError的子類。當連接嘗試被對端中止時將被引發。對應於Linux系統調用時的errno中的 ECONNABORTED。
        ConnectionRefusedError ConnectionError的子類。當連接嘗試被對端拒絕時將被引發。對應於Linux系統調用時的errno中的 ECONNREFUSED。
        ConnectionResetError ConnectionError的子類。當連接被對端重置時將被引發。對應於Linux系統調用時的errno中的 ECONNRESET。
    FileExistsError OSError的子類。當試圖創建一個已存在的文件或目錄時將被引發。對應於Linux系統調用時的errno中的 EEXIST。
    FileNotFoundError OSError的子類。當所請求的文件或目錄不存在時引發。對應於Linux系統調用時的errno中的 ENOENT。
    InterruptedError OSError的子類。當系統調用被輸入信號中斷時引發。對應於Linux系統調用時的errno中的 EINTR。
    IsADirectoryError OSError的子類。當請求對一個目錄執行文件操作時引發。對應於Linux系統調用時的errno中的 EISDIR。
    NotADirectoryError OSError的子類。當請求對一個非目錄對象執行目錄操作時引發。對應於Linux系統調用時的errno中的 ENOTDIR。
    PermissionError OSError的子類。當沒有對相應文件操作權限的時候引發。對應於Linux系統調用時的errno中的 EACCESS和 EPERM
    ProcessLookupError OSError的子類。當給定的進程不存在時引發。對應於Linux系統調用時的errno中的 ESRCH。
    TimeoutError OSError的子類。當一個系統函數發生系統級超時的情況下將被引發。對應於Linux系統調用時的errno中的 ETIMEDOUT。
ReferenceError 在弱引用訪問某個已被垃圾回收的屬性時引發,關於弱引用可參見werkref模塊。
RuntimeError 當檢測到一個不屬於任何其他類別的錯誤時引發。
    NotImplementedError RuntimeError的子類。當基類的抽象方法需要派生類實現,而派生類未實現時引發。
    RecursionError RuntimeError的子類。Python解釋器檢測發現超過最大遞歸深度時引發。
SyntaxError 解析器語法錯誤。
    IndentationError SyntaxError的子類。縮進錯誤時引發。
        TabError IndentationError的子類。當縮進包含對製表符和空格符不一致的使用時引發。
SystemError Python解釋器中的內部錯誤。
TypeError 當一個操作或函數被應用於類型不適當的對象時引發。
ValueError 當操作或函數接收到具有正確類型但值不適合的參數時引發。
    UnicodeError ValueError的子類。Unicode編碼或解碼錯誤時引發。
        UnicodeEncodeError UnicodeError的子類。Unicode編碼錯誤。
        UnicodeDecodeError UnicodeError的子類。Unicode解碼錯誤。
        UnicodeTranslateError UnicodeError的子類。在轉換過程中產生的與Unicode相關的錯誤。
以下異常繼承自:BaseException -> Exception -> ArithmeticError
FloatingPointError 目前未被使用。
OverflowError 當一個運算結果大到無法表示時將被引發。
ZeroDivisionError 當除法或取餘運算的第二個參數爲零時將被引發。
以下異常繼承自:BaseException -> Exception -> LookupError
IndexError 序列的下標超出範圍時引發。
KeyError 映射(字典)中未找到鍵時引發。
以下警告繼承自:BaseException -> Exception -> Warning
    DeprecationWarning 與已棄用特性相關警告的基類。
    PendingDeprecationWarning 對於已過時並預計在未來棄用,但目前尚未棄用的特性相關警告的基類。
    RuntimeWarning 與模糊的運行時行爲相關的警告的基類。
    SyntaxWarning 與模糊的語法相關的警告的基類。
    UserWarning 用戶代碼所產生警告的基類。
    FutureWarning 與已棄用特性相關警告的基類。
    ImportWarning 與在模塊導入中可能的錯誤相關的警告的基類。
    UnicodeWarning 與 Unicode 相關的警告的基類。
    BytesWarning 與 bytes 和 bytearray 相關的警告的基類。
    ResourceWarning 與資源使用相關的警告的基類。 會被默認的警告過濾器忽略。

 

 

 

  (2)自定義異常

可以通過繼承Exceptions類而定義自己的異常。定義完之後,可以使用raise語句引發這個新的異常,如下例所示:

 

class NetworkError(Exception):
    pass
    
raise NetworkError('Cannot find host')

下例顯示瞭如何在自定義異常中帶有多個異常值:

class NetworkError(Exception):
    def __init__(self, errno, msg):
        self.args = (errno, msg)    # 賦值給args是必須的,否則用戶無法看到自定義異常的任何細節提示信息
        self.errno = errno
        self.msg = msg

raise NetworkError(1, 'No response')

使用繼承機制將異常組成一個層次結構:

class NetworkError(Exception): pass
    
class HostnameError(NetworkError): 
    pass
class TimeoutError(NetworkError): 
    pass

# 以下爲引發並捕捉自定義異常
try:
    raise TimeoutError('Time out')
except NetworkError as e:
    if type(e) is TimeoutError:
      pass

 

 

 

  (3)主動引發異常

 

Assert(斷言)

斷言的基本目的是:與其讓程序在在將來不知某個時候崩潰,不如在程序中不符合某個預判條件時,主動讓程序崩潰。 當斷言條件不滿足時,會引發AssertionError異常。assert的格式爲:

assert condition [,msg]

其中,condition是一個表達式,其值若爲False時,assert語句就會引發AssertionError異常。 可以在assert語句後添加字符串msg,用來在發生異常時,提示預先設置的字符串信息。

使用舉例:

age = -1
assert 0 < age < 100 'The age is fantastic.'  # 執行本句時,會引發AssertionError異常,並會提示:The age is fantastic.

assert語句常和 if __debug__語句一起使用。在調試模式中,只讀變量__debug__的值爲True,可以隨意地在代碼中加入assert和調試檢查。 在最優模式中(通過-O指定),__debug__爲False,將省略所有這些額外的檢查。

 

 

raise

使用raise語句可主動引發異常,raise語句的格式爲:

raise Exception([value])

如果raise語句沒有帶任何參數,將會再次引發最近一次生成的異常。

raise使用舉例:

raise KeyError('abc')   # 引發KeyError異常,並提示字符串 'abc'

 

 

 

  (4)捕捉異常

通常,使用捕捉異常的代碼結構要比使用多個if-else語句判斷來得更清晰,而且執行效率也幾乎沒什麼損失,故應該在程序中儘可能使用try/except語句來查錯。 一般except語句捕捉並處理完異常後,程序將繼續執行跟在最後一個except代碼塊中的語句,程序並不會返回發生異常時的位置。

如果異常在函數內引發而未被處理,它就會向上傳遞到函數調用的地方,如果在那裏也沒有被處理,就會繼續向上傳遞,直到主程序(全局作用域)。 如果主程序裏也沒有處理,程序會帶着堆棧跟蹤停止。

如果需要,也可以把未捕捉的異常傳遞給用戶自定義的函數sys.excepthook()進行處理。

 

● try-except結構:

捕捉單個異常:

try:
    x = 2/0
except ZeroDivisionError:
    print 'The divisor is zero'

捕捉多個異常:

# 方法一:
try:
    x = 2/'a'
except ZeroDivisionError:
    pass
except TypeError:
    pass
    
# 方法二:
try:
    x = 2/'a'
except (ZeroDivisionError, TypeError, NameError):   
    print('oops!')

捕捉所有異常:

try:
    x = 2/0
except Exception:   # 這裏使用基類Exception可捕捉除了“鍵盤中斷”和“程序退出”的所有異常
    pass

捕捉異常並訪問異常對象:

try:
    x = 2/0
except Exception as e:
    print(e)

這裏生成的異常實例e具有一些標準屬性,列舉如下:

e.args:引發異常時提供的參數元組,一般包含有描述該錯誤的字符串。

e.__cause__:使用顯式關聯異常時的前一個異常。

e.__context__:使用隱式關聯異常時的前一個異常。

e.__traceback__:與異常相關的跟蹤對象。

 

● try-except-else結構

當try中的語句沒有發生異常請跨下,運行else語句塊中內容。

示例:

try:
    f = open('foo', 'r')
except IOError as e:
    print(e)
else:
    data = f.read()
    f.close()

 

● try-except-finally結構

finally語句塊用於無論try是否有異常,都要運行的代碼。如果沒有引發異常,finally子句中的代碼將在try的代碼塊執行完後立即執行。 如果捕捉到了異常,則finally中的內容先運行,然後再運行except語句塊中的內容。

示例:

try:
    f = open('foo', 'r')
    data = f.read()    
except IOError as e:
    print(e)
finally:
    f.close()   # 無論前面發生什麼,都會關閉文件

 

● try-except-else-finally結構

else和finally也可以組合在一起使用。

示例:

try:
    f = open('foo', 'r')
except IOError as e:
    print(e)
else:
    data = f.read()
finally:
    f.close()

 

 

 (5)error模塊

errno模塊爲各種操作系統調用返回的整數錯誤代碼定義了符號名稱,這些整數代碼通常可在OSError(別名IOError)異常的errno屬性中找到。

os.strerror()函數可以將整數的錯誤代碼轉換爲字符串信息。

errno模塊的errorcode字典,記錄了當前操作系統支持的錯誤代碼和POSIX符號名稱的對應關係,下表列舉了部分常用的錯誤代碼。

OSError中的錯誤代碼信息:

try:
    f = open(r'xxxxxx', 'r')
except Exception as e:
    print(e.errno)
    print(e.args)

# 運行結果爲:
2
(2, 'No such file or directory')

os.strerror()使用示例:

import os
import errno
print(os.strerror(2))

# 運行結果爲錯誤代碼2的字符串含義:No such file or directory'

 

常見POSIX錯誤代碼

錯誤代碼 名稱 描述
1 EPERM 操作未得到許可
2 ENOENT 文件或目錄不存在
3 ESRCH 進程不存在
4 EINTR 系統調用被中斷
5 EIO I/O錯誤
6 ENXIO 設備或地址不存在
7 E2BIG 參數列表過長
8 ENOEXEC 訪問被拒絕
9 EBADF 錯誤的文件編號
10 ECHILD 無子進程
11 EAGAIN 再試
12 ENOMEM 內存不足
13 EACCESS 訪問被拒絕
14 EFAULT 錯誤的地址
15 ENOTBLK 需要塊設備
16 EBUSY 設備或資源方面
17 EEXIST 文件存在
18 EXDEV 跨設備鏈接
19 ENODEV 沒有這個設備
20 ENOTDIR 不是一個目錄
21 EISDIR 是一個目錄
22 EINVAL 無效參數
23 ENFILE 文件表溢出
24 EMFILE 打開文件過多
25 ENOTTY 不是一個終端
26 ETXTBSY 文本文件忙
27 EFBIG 文件過大
28 ENOSPC 設備上無剩餘空間
29 ESPIPE 非法尋址
30 EROFS 只讀文件系統
31 EMLINK 鏈接過多
32 EPIPE 管道已損壞
33 EDOM 數學參數在函數作用域之外
34 ERANGE 無法表示的數學結果
35 EDEADLOCK 文件鎖定死鎖錯誤
36 ENAMETOOLONG 文件名過長
37 ENOLCK 無可用記錄鎖定
38 ENOSYS 函數無法實現
39 ENOTEMPTY 目錄不爲空
40 ELOOP 遇到過多的符號鏈接
84 EILSEQ 非法的字節序列
85 ERESTART 中斷系統調用需重啓
86 ESTRPIPE 流管道錯誤
87 EUSERS 用戶過多
88 ENOTSOCK 非套接字上的套接字操作
89 EDESTADDRREQ 需要目的地址
90 EMSGSIZE 消息過長
91 EPROTOTYPE 套接字的協議類型錯誤
92 ENOPROTOOPT 協議不可用
93 EPROTONOSUPPORT 不支持協議
94 ESOCKTNOSUPPORT 套接字類型不受支持
95 ENOTSUP 操作被遠端支持
96 EPFNOSUPPORT 不支持協議族
97 EAFNOSUPPORT 協議不支持地址族
98 EADDRINUSE 地址已使用
99 EADDRNOTAVAIL 無法分配請求的地址
100 ENETDOWN 網絡已關閉
101 ENETUNREACH 網絡不可到達
102 ENETRESET 網絡由於重置中斷連接
103 ECONNABORTED 軟件導致連接中斷
104 ECONNRESET 對等端已將連接重置
105 ENOBUFS 無可用緩存空間
106 EISCONN 傳輸端點已經連接
107 ENOTCONN 傳輸端點未連接
108 ESHUTDOWN 無法在傳輸端點關閉後發送
109 ETOOMANYREFS 引用過多:無法連接
110 ETIMEDOUT 連接超時
111 ECONNREFUSED 連接被拒絕
112 EHOSTDOWN 主機已關閉
113 EHOSTUNREACH 無路由通向主機
114 EALREADY 操作已經在進行中
115 EINPROGRESS 操作正在進行
116 ESTALE 失效的NFS文件句柄
125 ECANCELED 操作取消
126 ENOKEY 無此鍵
127 EKEYEXPIRED 鍵過期
128 EKEYREVOKED 鍵被撤回
129 EKEYREJECTED 鍵被服務拒絕
130 EOWNERDEAD 擁有者已不存在
131 ENOTRECOVERABLE 狀態不可恢復
132 ERFKILL 操作由於RF-KILL無法進行

 

 

 (6)with語句

with語句支持在“上下文管理器”對象的控制下,執行一系列語句。常用於管理各種系統資源(如文件、鎖、連接等)。 當程序中發生異常,而導致脫離正常的釋放資源語句時,只要用了with,就可以保證在離開with語句塊時自動釋放這些資源。下面是2個簡單的例子:

自動關閉文件對象

with open('a.txt', 'w') as f:
    f.write('xyz\n')
    ......
    f.write('done\n')
# 當程序離開with語句塊時,with語句會自動關閉已打開的文件

自動釋放鎖

import threading
lock = threading.Lock()
with lock:
    ......
# 當程序離開with語句塊時,with語句會自動釋放這個鎖

 

● with語句的一般語法:

with obj [as var]
    statements

obj對象需要實現__enter__()方法和__exit__()方法來支持with語句。當執行with obj語句時,會自動調用obj.__enter__()方法, 該方法的返回值將被放入指定變量var中。

當程序離開with語句塊時,會自動調用obj.__exit__()方法,其入參形式爲:__exit__(type, value, traceback), 三個入參分別爲:當前異常的類型、值、跟蹤信息。__exit__()方法返回True或False(表示被引發的異常是否得到了處理)

以下爲一個用戶自定義類支持with的例子,這個類支持對已有列表進行一系列修改, 但這些修改只有在沒有發生異常時纔會生效,否則原始列表將保持不變。

class ListTransaction(object):
    def __init__(self, thelist):
        self.thelist = thelist
    def __enter__(self):
        self.workingcopy = list(self.thelist)
        return self.workingcopy
    def __exit__(self, type, value, tb):
        if type is None:
            self.thelist[:] = self.workingcopy
        return
        
# 使用with語句:
items = [1,2,3]
try:
    with ListTransaction(items) as working:
        working.append(4)
        working.append(5)
        raise RuntimeError()
except RuntimeError:
    pass
print(items)

# 由於在離開with語句塊時發生了異常,因此__exit__()的入參type不爲None,最終結果爲:[1,2,3]

 

● 使用 contextlib 模塊

使用 contextlib 模塊可以更加方便地編寫上例那樣的支持 With 上下文管理語句的類。 它提供了一個 @contextmanager 裝飾器,用戶使用它裝飾某個“函數”,達到上面類似的效果。 下面是一個使用 @contextmanager 裝飾器的例子:

from contextlib import contextmanager
@contextmanager
def ListTransaction(thelist):
    workingcopy = list(thelist)
    yield workingcopy
    # 僅在沒有出現錯誤時,纔會修改原始列表
    thelist[:] = workingcopy

上面的程序中,裝飾器將傳遞給 yield 的值用作了 __enter__() 方法的返回值(即提供給外部的 With 語句)。在退出 With (即調用 __exit__() 方法)時,執行將在 yield 語句後恢復 (即把原始的 thelist 列表的值改掉)。

如果 With 上下文中引發了異常,它將以異常形式出現在生成器函數中。

 

 

 

 

返回目錄

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章