GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing python.github.io — 8. 錯誤和例外 — Python 3.15.2 說明文件 8. 錯誤和例外¶到目前為止還沒有提到錯誤訊息,但如果你嘗試運行範例,你可能會發現一些錯誤訊息。常見的(至少)兩種不同的錯誤類別為:語法錯誤 (syntax error) 和例外 (exception)。 8.1. 語法錯誤 (Syntax Error)¶語法錯誤又稱剖析錯誤 (parsing error),它或許是學習 Python 的過程最常聽見的抱怨: >>> while True print('Hello world') File "<stdin>", line 1 while True print('Hello world') ^^^^^ SyntaxError: invalid syntax 剖析器 (parser) 會重複犯錯的那一行,並用一個小箭頭指向該行檢測到錯誤的地方,但請注意這並非都會是應該去修改的地方。此例中,錯誤是在 檔案名稱(此例中為 8.2. 例外 (Exception)¶即使一段陳述式或運算式使用了正確的語法,嘗試執行時仍可能導致錯誤。執行時檢測到的錯誤稱為例外,例外不一定都很嚴重:你很快就能學會在 Python 程式中如何處理它們。不過大多數的例外不會被程式處理,並且會顯示如下的錯誤訊息: >>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in <module> 10 * (1/0) ~^~ ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in <module> 4 + spam*3 ^^^^ NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> '2' + 2 ~~~~^~~ TypeError: can only concatenate str (not "int") to str 錯誤訊息的最後一行指示發生了什麼事。例外有不同的類型,而類型名稱會作為訊息的一部份被印出。範例中的例外類型為: 此行其餘部分,根據例外的類型及導致例外的原因,說明例外的細節。 錯誤訊息的開頭,用堆疊回溯 (stack traceback) 的形式顯示發生例外的語境。一般來說,它含有一個列出源程式碼行 (source line) 的堆疊回溯;但它不會顯示從標準輸入中讀取的程式碼。 內建的例外章節列出內建的例外及它們的意義。 8.3. 處理例外¶編寫程式處理選定的例外是可行的。以下範例會要求使用者輸入內容,直到有效的整數被輸入為止,但它允許使用者中斷程式(使用 Control-C 或作業系統支援的指令);請注意,由使用者產生的程式中斷會引發 >>> while True: ... try: ... x = int(input("Please enter a number: ")) ... break ... except ValueError: ... print("Oops! That was no valid number. Try again...") ...
... except RuntimeError, TypeError, NameError: ... pass
class B(Exception): pass class C(B): pass class D(C): pass for cls in [B, C, D]: try: raise cls() except D: print("D") except C: print("C") except B: print("B") 請注意,如果 except 子句的順序被反轉(把 當例外發生時,它可能有相關聯的值,也就是例外的引數。引數的存在與否及它的類型,是取決於例外的類型。 except 子句可以在例外名稱後面指定一個變數。這個變數被綁定到一個例外實例 (instance),其引數通常儲存在 >>> try: ... raise Exception('spam', 'eggs') ... except Exception as inst: ... print(type(inst)) # 例外的型別 ... print(inst.args) # 儲存在 .args 中的引數 ... print(inst) # __str__ 使得引數可以直接被印出, ... # 但可能在例外的子類別中被覆蓋 ... x, y = inst.args # 解包引數 ... print('x =', x) ... print('y =', y) ... <class 'Exception'> ('spam', 'eggs') ('spam', 'eggs') x = spam y = eggs 例外的
處理 import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except OSError as err: print("OS error:", err) except ValueError: print("Could not convert data to an integer.") except Exception as err: print(f"Unexpected {err=}, {type(err)=}") raise
for arg in sys.argv[1:]: try: f = open(arg, 'r') except OSError: print('cannot open', arg) else: print(arg, 'has', len(f.readlines()), 'lines') f.close() 使用 例外的處理者不僅處理 try 子句內立即發生的例外,還處理 try 子句內(即使是間接地)呼叫的函式內部發生的例外。例如: >>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError as err: ... print('Handling run-time error:', err) ... Handling run-time error: division by zero 8.4. 引發例外¶
>>> raise NameError('HiThere') Traceback (most recent call last): File "<stdin>", line 1, in <module> raise NameError('HiThere') NameError: HiThere
raise ValueError # 'raise ValueError()' 的簡寫 如果你只想判斷是否引發了例外,但並不打算處理它,則可以使用簡單的 >>> try: ... raise NameError('HiThere') ... except NameError: ... print('An exception flew by!') ... raise ... An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, in <module> raise NameError('HiThere') NameError: HiThere 8.5. 例外鏈接 (Exception Chaining)¶如果在 >>> try: ... open("database.sqlite") ... except OSError: ... raise RuntimeError("unable to handle error") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> open("database.sqlite") ~~~~^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> raise RuntimeError("unable to handle error") RuntimeError: unable to handle error 為了表明一個例外是另一個例外直接造成的結果, # exc 必須是例外實例或 None。 raise RuntimeError from exc 要變換例外時,這種方式很有用。例如: >>> def func(): ... raise ConnectionError ... >>> try: ... func() ... except ConnectionError as exc: ... raise RuntimeError('Failed to open database') from exc ... Traceback (most recent call last): File "<stdin>", line 2, in <module> func() ~~~~^^ File "<stdin>", line 2, in func ConnectionError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> raise RuntimeError('Failed to open database') from exc RuntimeError: Failed to open database 它也容許使用慣用語 >>> try: ... open('database.sqlite') ... except OSError: ... raise RuntimeError from None ... Traceback (most recent call last): File "<stdin>", line 4, in <module> raise RuntimeError from None RuntimeError 更多關於鏈接機制的資訊,詳見內建的例外。 8.6. 使用者自定的例外¶程式可以透過建立新的例外 class 來命名自己的例外(深入了解 Python class,詳見Class(類別))。不論是直接還是間接地,例外通常應該從 例外 class 可被定義來做任何其他 class 能夠做的事,但通常會讓它維持簡單,只提供一些屬性,讓關於錯誤的資訊可被例外的處理者抽取出來。 大多數的例外定義,都會以「Error」作為名稱結尾,類似於標準例外的命名。 許多標準模組會定義它們自己的例外,以報告在其定義的函式中發生的錯誤。 8.7. 定義清理動作¶
>>> try: ... raise KeyboardInterrupt ... finally: ... print('Goodbye, world!') ... Goodbye, world! Traceback (most recent call last): File "<stdin>", line 2, in <module> raise KeyboardInterrupt KeyboardInterrupt 如果
例如: >>> def bool_return(): ... try: ... return True ... finally: ... return False ... >>> bool_return() False 另一個比較複雜的範例: >>> def divide(x, y): ... try: ... result = x / y ... except ZeroDivisionError: ... print("division by zero!") ... else: ... print("result is", result) ... finally: ... print("executing finally clause") ... >>> divide(2, 1) result is 2.0 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "<stdin>", line 1, in <module> divide("2", "1") ~~~~~~^^^^^^^^^^ File "<stdin>", line 3, in divide result = x / y ~~^~~ TypeError: unsupported operand type(s) for /: 'str' and 'str' 如你所見, 在真實應用程式中, 8.8. 預定義的清理動作¶某些物件定義了在物件不再被需要時的標準清理動作,無論使用該物件的作業是成功或失敗。請看以下範例,它嘗試開啟一個檔案,並印出檔案內容至螢幕。 for line in open("myfile.txt"): print(line, end="") 這段程式碼的問題在於,執行完該程式碼後,它讓檔案在一段不確定的時間內處於開啟狀態。在簡單腳本中這不是問題,但對於較大的應用程式來說可能會是個問題。 with open("myfile.txt") as f: for line in f: print(line, end="") 陳述式執行完畢後,就算是在處理內容時遇到問題,檔案 f 總是會被關閉。和檔案一樣,提供預定義清理動作的物件會在說明文件中表明這一點。 8.9. 引發及處理多個無關的例外¶在某些情況下,必須回報已經發生的多個例外。在並行框架 (concurrency framework) 中經常會出現這種情況,當平行的 (parallel) 某些任務可能已經失效,但還有其他用例 (use case) 希望能繼續執行並收集多個例外,而不是只有引發第一個例外時。 內建的 >>> def f(): ... excs = [OSError('error 1'), SystemError('error 2')] ... raise ExceptionGroup('there were problems', excs) ... >>> f() + Exception Group Traceback (most recent call last): | File "<stdin>", line 1, in <module> | f() | ~^^ | File "<stdin>", line 3, in f | raise ExceptionGroup('there were problems', excs) | ExceptionGroup: there were problems (2 sub-exceptions) +-+---------------- 1 ---------------- | OSError: error 1 +---------------- 2 ---------------- | SystemError: error 2 +------------------------------------ >>> try: ... f() ... except Exception as e: ... print(f'caught {type(e)}: {e}') ... caught <class 'ExceptionGroup'>: there were problems (2 sub-exceptions) >>> 若使用 >>> def f(): ... raise ExceptionGroup( ... "group1", ... [ ... OSError(1), ... SystemError(2), ... ExceptionGroup( ... "group2", ... [ ... OSError(3), ... RecursionError(4) ... ] ... ) ... ] ... ) ... >>> try: ... f() ... except* OSError as e: ... print("There were OSErrors") ... except* SystemError as e: ... print("There were SystemErrors") ... There were OSErrors There were SystemErrors + Exception Group Traceback (most recent call last): | File "<stdin>", line 2, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise ExceptionGroup( | ...<12 lines>... | ) | ExceptionGroup: group1 (1 sub-exception) +-+---------------- 1 ---------------- | ExceptionGroup: group2 (1 sub-exception) +-+---------------- 1 ---------------- | RecursionError: 4 +------------------------------------ >>> 請注意,被巢套在例外群組中的例外必須是實例,而不是類型。這是因為在實務上,這些例外通常是已經被程式引發並捕獲的例外,類似以下的模式: >>> excs = [] >>> for test in tests: ... try: ... test.run() ... except Exception as e: ... excs.append(e) ... >>> if excs: ... raise ExceptionGroup("Test Failures", excs) ... 8.10. 用註解使例外更詳細¶當一個例外是為了被引發而建立時,它通常會伴隨著一些資訊被初始化,這些資訊描述了當下發生的錯誤。在某些情況,在例外被捕獲之後添加資訊會很有用。為此,例外具有一個 >>> try: ... raise TypeError('bad type') ... except Exception as e: ... e.add_note('Add some information') ... e.add_note('Add some more information') ... raise ... Traceback (most recent call last): File "<stdin>", line 2, in <module> raise TypeError('bad type') TypeError: bad type Add some information Add some more information >>> 例如,在將例外收集到例外群組中時,我們可能希望為各個錯誤添加一些上下文的資訊。在以下範例中,群組中的每個例外都有一條註解,指示此錯誤是在何時發生。 >>> def f(): ... raise OSError('operation failed') ... >>> excs = [] >>> for i in range(3): ... try: ... f() ... except Exception as e: ... e.add_note(f'Happened in Iteration {i+1}') ... excs.append(e) ... >>> raise ExceptionGroup('We have some problems', excs) + Exception Group Traceback (most recent call last): | File "<stdin>", line 1, in <module> | raise ExceptionGroup('We have some problems', excs) | ExceptionGroup: We have some problems (3 sub-exceptions) +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "<stdin>", line 3, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 1 +---------------- 2 ---------------- | Traceback (most recent call last): | File "<stdin>", line 3, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 2 +---------------- 3 ---------------- | Traceback (most recent call last): | File "<stdin>", line 3, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 3 +------------------------------------ >>> |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |