程序中經常會出錯,常見的錯誤包括但不限於:
語法錯誤:“SyntaxError:invalid syntax”
異常:xxError,如NameError、TypeError、IndentationError、ModuleNotFoundError等
語法錯誤,在運行前就可以發現。如果使用PyCharm會有紅色波浪線提醒你,請檢查拼寫、縮進、符號等是否符合語法。(SyntaxError也是一種異常,但是因為它比較特殊,在運行前就可以檢查出來,所以單獨說。)
異常情況很多,需要根據報錯內容具體分析。下面我們看看異常到底是什麼以及如何處理異常。
程序執行時往往會出現預期之外的錯誤,也就是異常。
這些錯誤未必是程序設計的問題,也可能是用戶非法輸入、網絡問題等導致程序出錯。
例如一個計算器程序,用戶輸入1/0的時候,0作分母是無意義的。因此程序無法正常執行,引發報錯。
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
實際程序中,我們可能遇到各種異常。
內置異常 — Python 3.10.4 文檔裡提供了大多數可能的異常,如IO異常,迭代異常、編碼錯誤異常等等。
BaseException
是所有異常的基類,它可以用來捕獲所有異常。
但更常用是Exception
。Exception
是所有內置的非系統退出類異常的基類。 所有用戶自定義異常也應當派生自此類。
一般用try-except 語句來提前預防錯誤。
語法格式:
try:
...
執行一些可能出錯的操作
except 異常類型:
...
對出錯進行一個說明和處理
例如,我們寫了一個從用戶輸入讀取a,b,並計算a/b的程序。
用戶可能輸入一個非數字內容,引發ValueError
,也可能輸入0作為除數,引發ZeroDivisionError
。
於是我們把可能出錯的語句放在try
裡面,並且用 except
捕捉錯誤。
try:
a = int(input('a= '))
b = int(input('b= '))
print('a/b= ',a/b)
except (ValueError,ZeroDivisionError):
print("無效輸入,請重試")
try
語句的工作原理如下:
首先,執行 try 子句 。
如果沒有觸發異常,則跳過 except 子句,try
語句執行完畢。
如果在執行 try
子句時發生了異常,則跳過該子句中剩下的部分。 如果異常的類型與 except
關鍵字後指定的異常相匹配,則會執行 except 子句,然後跳到 try/except 代碼塊之後繼續執行。如果發生的異常與 except 子句 中指定的異常不匹配,則它會被傳遞到外部的 try
語句中;如果沒有找到處理程序,則它是一個 未處理異常 且執行將終止並輸出報錯信息。
except 子句 可以用帶圓括號的元組來指定多個異常,例如:
except (RuntimeError, TypeError, NameError):
pass
try
後面可以接多個except
,來捕獲多種異常。如果異常被前面的except捕獲了,則後面的except不會繼續執行:
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print(err.args)
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except BaseException as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
except 子句 可以在異常名稱後面用as
指定一個變量。 這個變量會綁定到一個異常實例並將參數存儲在 instance.args
中。 print(err)會調用異常類的__str__()
方法,獲取表示異常的字符串。
try
… except
語句具有可選的 else 子句,該子句如果存在,它必須放在所有 except 子句 之後。 else會在 try 子句 沒有引發異常時執行。 例如:
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
語句還有一個可選子句finally
,用於定義在所有情況下都必須要執行的清理操作。例如:
try:
raise KeyboardInterrupt
finally:
print('Goodbye, world!')
不論 try
語句是否觸發異常,都會執行 finally
子句。在實際應用程序中,finally
子句對於釋放外部資源(例如文件或者網絡連接)非常有用。
with 語句是try-finally的一種簡化寫法,相當於在後面隱藏了一個finally來清理資源:
with open("myfile.txt") as f:
for line in f:
print(line, end="")
try-finally 特殊情形:
以下內容介紹了幾種比較復雜的觸發異常情景:
如果執行 try
子句期間觸發了某個異常,則某個 except
子句應處理該異常。如果該異常沒有 except
子句處理,在 finally
子句執行後會被重新觸發。
except
或 else
子句執行期間也會觸發異常。 同樣,該異常會在 finally
子句執行之後被重新觸發。
如果 finally
子句中包含 break
、continue
或 return
等語句,異常將不會被重新引發。
如果執行 try
語句時遇到 break
,、continue
或 return
語句,則 finally
子句在執行 break
、continue
或 return
語句之前執行。
如果 finally
子句中包含 return
語句,則返回值來自 finally
子句的某個 return
語句的返回值,而不是來自 try
子句的 return
語句的返回值。
raise
語句可以拋出指定的異常:
raise 異常
raise NameError('HiThere')
在捕獲異常後如果不想處理,可以用單個raise
重新拋出異常:
try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
raise
raise
支持可選的 from
子句,用於啟用鏈式異常。
如:raise RuntimeError from exc
轉換異常時,這種方式很有用。例如:
def func():
raise ConnectionError
try:
func()
except ConnectionError as exc:
raise RuntimeError('Failed to open database') from exc
異常鏈會在 except
或 finally
子句內部引發異常時自動生成。 這可以通過使用 from None
這樣的寫法來禁用:
try:
open('database.sqlite')
except OSError:
raise RuntimeError from None
用戶可以通過自定義繼承Exception
的類來實現自己的異常。大多數異常命名都以 “Error” 結尾,類似標准異常的命名。(第9章類將介紹如何定義類)
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
raise MyError(42)
except MyError as e:
print(e)