Following system colour scheme - Python 增強提案 Selected dark colour scheme - Python 增強提案 Selected light colour scheme - Python 增強提案

Python 增強提案

PEP 3151 – 重構作業系統和 IO 異常層次結構

作者:
Antoine Pitrou <solipsis at pitrou.net>
BDFL 委託
Barry Warsaw
狀態:
最終版
型別:
標準跟蹤
建立日期:
2010 年 7 月 21 日
Python 版本:
3.3
釋出歷史:

決議:
Python-Dev 訊息

目錄

摘要

標準異常層次結構是 Python 語言的重要組成部分。它具有兩個決定性特質:既通用又具有選擇性。通用性在於無論上下文如何(例如,無論您是嘗試將某個內容新增到整數、呼叫字串方法,還是將物件寫入套接字,對於錯誤的引數型別都會引發 TypeError),都可以引發並處理相同的異常型別。選擇性在於它允許使用者輕鬆處理(靜默、檢查、處理、儲存或封裝...)特定型別的錯誤條件,同時讓其他錯誤冒泡到更高的呼叫上下文。例如,您可以選擇捕獲 ZeroDivisionErrors,而不會影響其他 ArithmeticErrors(如 OverflowErrors)的預設處理。

本 PEP 提議修改異常層次結構的一部分,以更好地體現上述特質:與作業系統呼叫相關的錯誤(OSError、IOError、mmap.error、select.error 及其所有子類)。

基本原理

缺乏細粒度異常

當前各種 OS 相關異常不允許使用者輕鬆過濾所需的故障型別。例如,考慮在檔案存在時刪除檔案的任務。預先檢查(LBYL)的習語存在明顯的競爭條件

if os.path.exists(filename):
    os.remove(filename)

如果在呼叫 os.path.existsos.remove 之間,另一個執行緒或程序建立了名為 filename 的檔案,則該檔案將不會被刪除。這可能導致應用程式中的錯誤,甚至安全問題。

因此,解決方案是嘗試刪除檔案,如果檔案不存在則忽略錯誤(這種習語被稱為“更容易請求寬恕而不是請求許可”,即 EAFP)。細緻的程式碼將如下所示(這在 POSIX 和 Windows 系統下都適用)

try:
    os.remove(filename)
except OSError as e:
    if e.errno != errno.ENOENT:
        raise

甚至

try:
    os.remove(filename)
except EnvironmentError as e:
    if e.errno != errno.ENOENT:
        raise

這需要輸入更多的內容,並且還迫使使用者記住 errno 模組中各種神秘的助記符。它增加了額外的認知負擔,並且很快就會令人厭煩。因此,許多程式設計師會轉而編寫以下程式碼,它過於寬泛地抑制了異常

try:
    os.remove(filename)
except OSError:
    pass

os.remove 不僅在檔案不存在時會引發 OSError,在其他可能的情況下也會引發(例如,檔名指向目錄,或者當前程序沒有刪除檔案的許可權),這些都表明應用程式邏輯中存在錯誤,因此不應被靜默。程式設計師更願意編寫的程式碼應該是這樣的

try:
    os.remove(filename)
except FileNotFoundError:
    pass

相容性策略

重構異常層次結構顯然會改變至少某些現有程式碼的確切語義。雖然在不改變確切語義的情況下無法改善當前狀況,但可以定義一種更窄的相容性型別,我們稱之為 *有用相容性*。

為此,我們首先必須解釋我們所說的 *細緻* 和 *粗心* 異常處理。*粗心*(或“天真”)程式碼被定義為盲目捕獲任何 OSErrorIOErrorsocket.errormmap.errorWindowsErrorselect.error 而不檢查 errno 屬性的程式碼。這是因為這些異常型別過於寬泛,無法表示任何特定的含義。它們中的任何一個都可能因各種錯誤條件而引發,例如:壞的檔案描述符(通常表示程式設計錯誤)、未連線的套接字(同上)、套接字超時、檔案型別不匹配、無效引數、傳輸失敗、許可權不足、不存在的目錄、檔案系統已滿等。

(此外,某些異常的使用是不規則的;附錄 B 揭示了 select 模組的情況,它根據實現方式引發不同的異常)

*細緻* 程式碼定義為在捕獲上述任何異常時,檢查 errno 屬性以確定實際錯誤條件並根據其採取行動的程式碼。

然後我們可以定義 *有用相容性* 如下

  • 有用相容性不會使異常捕獲變得更窄,但對於 *粗心* 的異常捕獲程式碼,它可能會更廣。給定以下程式碼片段,在此 PEP 之前捕獲的所有異常在此 PEP 之後也將被捕獲,但反之則可能不成立(因為 OSErrorIOError 和其他異常的合併意味著 except 子句撒下的網略寬一些)
    try:
        ...
        os.remove(filename)
        ...
    except OSError:
        pass
    
  • 有用相容性不會改變 *細緻* 異常捕獲程式碼的行為。給定以下程式碼片段,無論是否實現了本 PEP,相同的錯誤都應該被靜默或重新引發
    try:
        os.remove(filename)
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise
    

這種折衷的理由是粗心程式碼無法真正得到幫助,但至少“工作”的程式碼不會突然引發錯誤並崩潰。這很重要,因為此類程式碼很可能存在於用作 cron 任務或自動化系統管理程式的指令碼中。

另一方面,細緻的程式碼不應受到懲罰。實際上,本 PEP 的一個目的是簡化細緻程式碼的編寫。

步驟 1:合併異常型別

解決方案的第一步是合併現有異常型別。提出以下更改

  • 將 socket.error 和 select.error 都別名為 OSError
  • 將 mmap.error 別名為 OSError
  • 將 WindowsError 和 VMSError 都別名為 OSError
  • 將 IOError 別名為 OSError
  • 將 EnvironmentError 合併到 OSError 中

這些更改中的每一個都不會保留精確的相容性,但會保留 *有用相容性*(參見上文“相容性”部分)。

這些更改中的每一個都可以單獨接受或拒絕,但當然,如果第一步被完全接受,則可以實現最大的影響。在這種情況下,IO 異常子層次結構將變為

+-- OSError   (replacing IOError, WindowsError, EnvironmentError, etc.)
    +-- io.BlockingIOError
    +-- io.UnsupportedOperation (also inherits from ValueError)
    +-- socket.gaierror
    +-- socket.herror
    +-- socket.timeout

理由

基本原理 部分所述,這第一步不僅為使用者呈現了更簡單的檢視,而且還允許 步驟 2 得到更好、更完整的解決(參見 先決條件)。

保留 OSError 作為通用 OS 相關異常的官方名稱的理由是,它比 IOError 更通用。 EnvironmentError 鍵入更繁瑣,也鮮為人知。

附錄 B 中的調查顯示,IOError 是目前標準庫中主要的錯誤。對於第三方 Python 程式碼,Google 程式碼搜尋顯示 IOError 在使用者程式碼中的流行度是 EnvironmentError 的十倍,是 OSError 的三倍 [3]。然而,由於中期不打算棄用 IOError,OSError 流行度較低並不是問題。

異常屬性

由於 WindowsError 合併到 OSError 中,後者在 Windows 下獲得了 winerror 屬性。在它沒有意義的情況下,它被設定為 None,就像 errnofilenamestrerror 屬性一樣(例如,當 OSError 直接由 Python 程式碼引發時)。

名稱的棄用

以下段落概述了舊異常名稱可能的棄用策略。然而,目前已決定將它們保留為別名。此決定可能會在 Python 4.0 釋出時進行修訂。

內建異常

棄用舊的內建異常不能透過攔截內建名稱空間中的所有查詢來直接完成,因為這些查詢對效能至關重要。我們也不能在物件級別操作,因為已棄用的名稱將被別名到未棄用的物件。

一種解決方案是在編譯時識別這些名稱,然後發出單獨的 LOAD_OLD_GLOBAL 操作碼,而不是常規的 LOAD_GLOBAL。當名稱不存在於全域性名稱空間中,而只存在於內建名稱空間中時,這個專門的操作碼將處理 DeprecationWarning(或 PendingDeprecationWarning,取決於所決定的策略)的輸出。這足以避免誤報(例如,如果有人在模組中定義自己的 OSError),並且漏報將很少見(例如,當有人透過 builtins 模組而不是直接訪問 OSError 時)。

模組級異常

上述方法不易使用,因為它需要在編譯程式碼物件時對某些模組進行特殊處理。然而,這些名稱在結構上可見性較低(它們不出現在內建名稱空間中),也鮮為人知,因此我們可能會決定讓它們存在於自己的名稱空間中。

步驟 2:定義其他子類

解決方案的第二步是透過定義子類來擴充套件層次結構,這些子類將針對特定的 errno 值而不是其父類被引發。具體哪些 errno 值尚待討論,但對現有異常匹配實踐的調查(參見 附錄 A)有助於我們提出所有值的一個合理子集。事實上,嘗試對映所有 errno 助記符似乎是愚蠢、毫無意義的,並且會汙染根名稱空間。

此外,在少數情況下,不同的 errno 值可能會引發相同的異常子類。例如,EAGAIN、EALREADY、EWOULDBLOCK 和 EINPROGRESS 都用於表示非阻塞套接字上的操作將阻塞(因此稍後需要再次嘗試)。因此,它們都可以引發相同的子類,並讓使用者在需要時檢查 errno 屬性(參見下文“異常屬性”)。

先決條件

步驟 1 是對此的鬆散先決條件。

先決條件,因為某些 errno 目前可以附加到不同的異常類:例如,ENOENT 可以根據上下文附加到 OSError 和 IOError。如果我們不想破壞 *有用相容性*,我們就不能讓 except OSError(或 IOError)無法匹配今天會成功的異常。

鬆散的,因為如果現有異常類未合併,我們可以決定部分解決步驟 2:例如,ENOENT 可能在以前引發 IOError 的情況下引發假設的 FileNotFoundError,但在其他情況下繼續引發 OSError。

如果新的子類使用多重繼承來匹配所有現有的超類(或者至少是 OSError 和 IOError,這可能是最普遍的),那麼對步驟 1 的依賴可以完全消除。然而,這將使層次結構更加複雜,因此使用者更難理解。

新異常類

以下是提議的子類暫定列表,以及描述和對映到它們的 errno 列表,以供討論

  • FileExistsError:嘗試建立已存在的檔案或目錄 (EEXIST)
  • FileNotFoundError:在所有請求檔案和目錄但不存在的情況下 (ENOENT)
  • IsADirectoryError:在目錄上請求檔案級操作(open(), os.remove()…) (EISDIR)
  • NotADirectoryError:在其他內容上請求目錄級操作 (ENOTDIR)
  • PermissionError:嘗試在沒有足夠訪問許可權的情況下執行操作——例如檔案系統許可權 (EACCES, EPERM)
  • BlockingIOError:操作將在設定為非阻塞操作的物件(例如套接字)上阻塞 (EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS);這是現有 io.BlockingIOError 的擴充套件作用
  • BrokenPipeError:嘗試寫入管道而另一端已關閉,或嘗試寫入已關閉寫入的套接字 (EPIPE, ESHUTDOWN)
  • InterruptedError:系統呼叫被傳入訊號中斷 (EINTR)
  • ConnectionAbortedError:連線嘗試被對端中止 (ECONNABORTED)
  • ConnectionRefusedError:連線被對端拒絕 (ECONNREFUSED)
  • ConnectionResetError:連線被對端重置 (ECONNRESET)
  • TimeoutError:連線超時 (ETIMEDOUT);這可以重新定義為通用超時異常,替換 socket.timeout,也適用於其他型別的超時(例如 Lock.acquire() 中的超時)
  • ChildProcessError:子程序上的操作失敗 (ECHILD);這主要由 wait() 系列函式引發。
  • ProcessLookupError:給定程序(例如,由其程序 ID 標識)不存在 (ESRCH)。

此外,提議包含以下異常類

  • ConnectionErrorConnectionAbortedErrorConnectionRefusedErrorConnectionResetError 的基類

以下圖表嘗試總結了擬議的新增內容,以及相應的 errno 值(如果適用)。子層次結構的根(OSError,假設 步驟 1 被完全接受)未顯示

+-- BlockingIOError        EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS
+-- ChildProcessError                                          ECHILD
+-- ConnectionError
    +-- BrokenPipeError                              EPIPE, ESHUTDOWN
    +-- ConnectionAbortedError                           ECONNABORTED
    +-- ConnectionRefusedError                           ECONNREFUSED
    +-- ConnectionResetError                               ECONNRESET
+-- FileExistsError                                            EEXIST
+-- FileNotFoundError                                          ENOENT
+-- InterruptedError                                            EINTR
+-- IsADirectoryError                                          EISDIR
+-- NotADirectoryError                                        ENOTDIR
+-- PermissionError                                     EACCES, EPERM
+-- ProcessLookupError                                          ESRCH
+-- TimeoutError                                            ETIMEDOUT

命名

可能會出現各種命名爭議。其中之一是所有異常類名稱是否都應以“Error”結尾。支援方認為這與異常層次結構的其他部分保持一致,反對派則認為這會增加冗長(特別是對於像 ConnectionAbortedError 這樣的長名稱)。

異常屬性

為了保持 *有用相容性*,這些子類仍應為超類上定義的各種異常屬性(例如 errnofilename 以及可選的 winerror)設定適當的值。

實施

由於提議子類的引發純粹基於 errno 的值,因此擴充套件模組(無論是標準模組還是第三方模組)應基本無需更改。

第一種可能性是調整 PyErr_SetFromErrno() 函式家族(Windows 下為 PyErr_SetFromWindowsErr())以引發適當的 OSError 子類。但是,這不會涵蓋直接引發 OSError 的 Python 程式碼,例如以下常見用法(在 Lib/tempfile.py 中可見)

raise IOError(_errno.EEXIST, "No usable temporary file name found")

Marc-Andre Lemburg 提出的第二種可能性是調整 OSError.__new__ 以例項化適當的子類。這具有同時涵蓋上述 Python 程式碼的優點。

可能的異議

名稱空間汙染

使異常層次結構更細粒度會使根(或內建)名稱空間更大。然而,這需要適度,因為

  • 僅提出了少數附加類;
  • 雖然標準異常型別存在於根名稱空間中,但它們透過使用駝峰命名法與其他內建函式在視覺上有所區別,而幾乎所有其他內建函式都使用小寫命名(除了 True、False、None、Ellipsis 和 NotImplemented)

另一種選擇是提供一個單獨的模組來包含更細粒度的異常,但這將違背鼓勵細緻程式碼而非粗心程式碼的目的,因為使用者必須首先匯入新模組,而不是使用已經可訪問的名稱。

早前討論

儘管這是首次提出如此正式的提案,但該想法在過去曾獲得非正式支援 [1];包括引入更細粒度的異常類以及合併 OSError 和 IOError。

單獨移除 WindowsError 作為 另一個 PEP 的一部分被討論並拒絕,但似乎存在一種共識,即與 OSError 的區別沒有意義。這至少支援將其與 OSError 別名。

實施

參考實現已整合到 Python 3.3 中。它以前在 http://hg.python.org/features/pep-3151/pep-3151 分支中開發,並在 bug 跟蹤器 http://bugs.python.org/issue12555 上進行跟蹤。它已在各種系統上成功測試:Linux、Windows、OpenIndiana 和 FreeBSD buildbots。

一個麻煩的來源是 OSErrorWindowsError 的各自建構函式不相容。解決方法是保留 OSError 簽名並新增第四個可選引數,以允許傳遞 Windows 錯誤程式碼(這與 POSIX errno 不同)。第四個引數儲存為 winerror,其 POSIX 轉換儲存為 errnoPyErr_SetFromWindowsErr* 函式已調整為使用正確的建構函式呼叫。

一個輕微的複雜情況是當使用 OSError 而不是 WindowsError 呼叫 PyErr_SetExcFromWindowsErr* 函式時:異常物件的 errno 屬性將儲存 Windows 錯誤程式碼(例如 ERROR_BROKEN_PIPE 的 109)而不是其 POSIX 轉換(例如 EPIPE 的 32),而現在它儲存的是 POSIX 轉換。對於非套接字錯誤程式碼,這僅發生在私有 _multiprocessing 模組中,該模組沒有相容性問題。

注意

對於套接字錯誤,errno 模組所反映的“POSIX errno”在數值上等於 WSAGetLastError 系統呼叫返回的 Windows 套接字錯誤程式碼

>>> errno.EWOULDBLOCK
10035
>>> errno.WSAEWOULDBLOCK
10035

可能的替代方案

模式匹配

另一種可能性是在捕獲異常時引入高階模式匹配語法。例如

try:
    os.remove(filename)
except OSError as e if e.errno == errno.ENOENT:
    pass

此提案的幾個問題

  • 它引入了新的語法,作者認為這比重構異常層次結構是一個更重大的改變
  • 它並沒有顯著減少輸入工作量
  • 它並沒有減輕程式設計師記住 errno 助記符的負擔

本 PEP 忽略的異常

本 PEP 忽略 EOFError,它在各種協議和檔案格式實現中(例如 GzipFile)表示輸入流截斷。EOFError 與作業系統或 IO 無關,它是更高層次上引發的邏輯錯誤。

本 PEP 還忽略了 SSLError,它由 ssl 模組引發,用於傳播 OpenSSL 庫發出的錯誤。SSLError 理想情況下將受益於類似但單獨的處理,因為它定義了自己的錯誤型別常量(ssl.SSL_ERROR_WANT_READ 等)。在 Python 3.2 中,當 SSLError 表示套接字超時時,它已被 socket.timeout 替換(參見 問題 10272)。

最後,socket.gaierrorsocket.herror 的命運尚未確定。雖然它們值得更清晰的名稱,但這可以與異常層次結構重組工作分開處理。

附錄 A:常見 errno 調查

這是標準庫及其測試中,作為 except 子句的一部分,檢查的各種 errno 助記符的快速清單。

與 OSError 相關的常見 errno

  • EBADF:無效檔案描述符(通常表示檔案描述符已關閉)
  • EEXIST:檔案或目錄已存在
  • EINTR:函式呼叫被中斷
  • EISDIR:是目錄
  • ENOTDIR:不是目錄
  • ENOENT:沒有此類檔案或目錄
  • EOPNOTSUPP:套接字不支援的操作(可能與現有 io.UnsupportedOperation 混淆)
  • EPERM:操作不允許(例如使用 os.setuid() 時)

與 IOError 相關的常見 errno

  • EACCES:許可權被拒絕(用於檔案系統操作)
  • EBADF:錯誤的檔案描述符(與 select.epoll);對只寫 GzipFile 進行讀操作,反之亦然
  • EBUSY:裝置或資源忙
  • EISDIR:是目錄(嘗試 open() 時)
  • ENODEV:無此類裝置
  • ENOENT:無此類檔案或目錄(嘗試 open() 時)
  • ETIMEDOUT:連線超時

與 socket.error 相關的常見 errno

所有這些錯誤也可能與普通的 IOError 相關聯,例如在套接字的檔案描述符上呼叫 read() 時。

  • EAGAIN:資源暫時不可用(在非阻塞套接字呼叫期間,connect() 除外)
  • EALREADY:連線已在進行中(在非阻塞 connect() 期間)
  • EINPROGRESS:操作正在進行中(在非阻塞 connect() 期間)
  • EINTR:函式呼叫被中斷
  • EISCONN:套接字已連線
  • ECONNABORTED:連線被對端中止(在 accept() 呼叫期間)
  • ECONNREFUSED:連線被對端拒絕
  • ECONNRESET:連線被對端重置
  • ENOTCONN:套接字未連線
  • ESHUTDOWN:傳輸端點關閉後無法傳送
  • EWOULDBLOCK:與 EAGAIN 相同的原因

與 select.error 相關的常見 errno

  • EINTR:函式呼叫被中斷

附錄 B:引發的作業系統和 IO 錯誤調查

關於 VMSError

VMSError 完全不被直譯器核心和標準庫使用。它是作為 Jean-François Piéronne 在 2002 年提交的 OpenVMS 補丁的一部分新增的 [4];引入 VMSError 的動機是它可能被第三方軟體包引發。

直譯器核心

PYTHONSTARTUP 的處理引發 IOError(但錯誤被丟棄)

$ PYTHONSTARTUP=foox ./python
Python 3.2a0 (py3k:82920M, Jul 16 2010, 22:53:23)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Could not open PYTHONSTARTUP
IOError: [Errno 2] No such file or directory: 'foox'

PyObject_Print() 在 ferror() 對 FILE * 引數(在原始碼樹中,總是 stdout 或 stderr)發出錯誤訊號時引發 IOError。

使用 mbcs 編碼進行 Unicode 編碼和解碼在某些錯誤條件下可能會引發 WindowsError。

標準庫

bz2

始終引發 IOError(未使用 OSError)

>>> bz2.BZ2File("foox", "rb")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> bz2.BZ2File("LICENSE", "rb").read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: invalid data stream
>>> bz2.BZ2File("/tmp/zzz.bz2", "wb").read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: file is not ready for reading

curses

未檢查。

dbm.gnu, dbm.ndbm

_dbm.error 和 _gdbm.error 繼承自 IOError

>>> dbm.gnu.open("foox")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_gdbm.error: [Errno 2] No such file or directory

fcntl

始終引發 IOError(未使用 OSError)。

imp 模組

對無效檔案描述符引發 IOError

>>> imp.load_source("foo", "foo", 123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 9] Bad file descriptor

io 模組

在 Unix 下嘗試開啟目錄時引發 IOError

>>> open("Python/", "r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 21] Is a directory: 'Python/'

對於不支援的操作,引發 IOError 或 io.UnsupportedOperation(繼承自前者)

>>> open("LICENSE").write("bar")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: not writable
>>> io.StringIO().fileno()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: fileno
>>> open("LICENSE").seek(1, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: can't do nonzero cur-relative seeks

當底層 I/O 層行為異常(即違反其應實現的 API)時,引發 IOError 或 TypeError。

當底層 OS 資源失效時引發 IOError

>>> f = open("LICENSE")
>>> os.close(f.fileno())
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 9] Bad file descriptor

...或用於實現特定的最佳化

>>> f = open("LICENSE")
>>> next(f)
'A. HISTORY OF THE SOFTWARE\n'
>>> f.tell()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: telling position disabled by next() call

當對非阻塞物件進行呼叫會阻塞時,引發 BlockingIOError(繼承自 IOError)。

mmap

在 Unix 下,始終引發其自己的 mmap.error(繼承自 EnvironmentError)

>>> mmap.mmap(123, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mmap.error: [Errno 9] Bad file descriptor
>>> mmap.mmap(os.open("/tmp", os.O_RDONLY), 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mmap.error: [Errno 13] Permission denied

然而,在 Windows 下,它主要引發 WindowsError(原始碼也顯示了幾處 mmap.error 的出現)

>>> fd = os.open("LICENSE", os.O_RDONLY)
>>> m = mmap.mmap(fd, 16384)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 5] Accès refusé
>>> sys.last_value.errno
13
>>> errno.errorcode[13]
'EACCES'

>>> m = mmap.mmap(-1, 4096)
>>> m.resize(16384)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 87] Paramètre incorrect
>>> sys.last_value.errno
22
>>> errno.errorcode[22]
'EINVAL'

多程序

未檢查。

os / posix

os(或 posix)模組始終引發 OSError,但在 Windows 下可能會改為引發 WindowsError。

ossaudiodev

始終引發 IOError(未使用 OSError)

>>> ossaudiodev.open("foo", "r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'foo'

readline

在各種檔案處理函式中引發 IOError

>>> readline.read_history_file("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> readline.read_init_file("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> readline.write_history_file("/dev/nonexistent")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 13] Permission denied

select

  • select() 和 poll 物件引發 select.error,它不繼承自任何東西(但 poll.modify() 引發 IOError);
  • epoll 物件引發 IOError;
  • kqueue 物件同時引發 OSError 和 IOError。

順便提一下,不派生自 EnvironmentError 意味著 select.error 沒有獲得有用的 errno 屬性。使用者程式碼必須檢查 args[0]

>>> signal.alarm(1); select.select([], [], [])
0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
select.error: (4, 'Interrupted system call')
>>> e = sys.last_value
>>> e
error(4, 'Interrupted system call')
>>> e.errno == errno.EINTR
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'error' object has no attribute 'errno'
>>> e.args[0] == errno.EINTR
True

signal

signal.ItimerError 繼承自 IOError。

socket

socket.error 繼承自 IOError。

sys

如果 GetVersionEx() 呼叫失敗,sys.getwindowsversion() 會引發帶有虛假錯誤號的 WindowsError。

time

在 time.time() 和 time.sleep() 的內部錯誤中引發 IOError。

zipimport

zipimporter.get_data() 可能引發 IOError。

致謝

Alyssa Coghlan 提供了重要輸入。

參考資料


來源:https://github.com/python/peps/blob/main/peps/pep-3151.rst

最後修改:2025-02-01 08:59:27 GMT