Python 例外處理 (Exception Handling)
例外(Exception)是程式執行時發生的錯誤。Python 提供 try...except 語法來處理例外,讓程式不會因為錯誤而中斷。
常見的例外類型
| 例外 | 說明 |
|---|---|
SyntaxError | 語法錯誤 |
NameError | 變數名稱不存在 |
TypeError | 型別錯誤 |
ValueError | 值錯誤 |
IndexError | 索引超出範圍 |
KeyError | 字典 key 不存在 |
FileNotFoundError | 檔案不存在 |
ZeroDivisionError | 除以零 |
AttributeError | 屬性不存在 |
ImportError | 匯入錯誤 |
# 一些會產生例外的程式碼
print(10 / 0) # ZeroDivisionError
print(int("hello")) # ValueError
print(my_var) # NameError
print([1, 2, 3][10]) # IndexError
print({"a": 1}["b"]) # KeyError
try...except
使用 try...except 捕捉例外:
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero!")
輸出:
Cannot divide by zero!
捕捉多種例外
try:
# 可能發生錯誤的程式碼
value = int(input("Enter a number: "))
result = 10 / value
except ValueError:
print("Please enter a valid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
一次捕捉多種例外
try:
value = int(input("Enter a number: "))
result = 10 / value
except (ValueError, ZeroDivisionError):
print("Invalid input!")
捕捉所有例外
try:
# 可能發生錯誤的程式碼
result = 10 / 0
except Exception as e:
print(f"An error occurred: {e}")
捕捉所有例外可能會隱藏真正的問題,建議只捕捉你知道如何處理的特定例外。
取得例外資訊
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error type: {type(e).__name__}")
print(f"Error message: {e}")
輸出:
Error type: ZeroDivisionError
Error message: division by zero
else 子句
else 在沒有例外時執行:
try:
result = 10 / 2
except ZeroDivisionError:
print("Cannot divide by zero!")
else:
print(f"Result: {result}") # 沒有例外時執行
輸出:
Result: 5.0
finally 子句
finally 無論是否發生例外都會執行,常用於清理資源:
try:
f = open("file.txt", "r")
content = f.read()
except FileNotFoundError:
print("File not found!")
finally:
print("Cleanup...")
# 確保檔案被關閉
if 'f' in locals() and not f.closed:
f.close()
完整的 try 語法
try:
# 可能發生錯誤的程式碼
pass
except ExceptionType1:
# 處理 ExceptionType1
pass
except ExceptionType2 as e:
# 處理 ExceptionType2,並取得例外資訊
pass
except Exception as e:
# 處理其他所有例外
pass
else:
# 沒有例外時執行
pass
finally:
# 無論如何都會執行
pass
raise 拋出例外
使用 raise 主動拋出例外:
def divide(a, b):
if b == 0:
raise ValueError("Divisor cannot be zero!")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(e) # Divisor cannot be zero!
重新拋出例外
try:
result = 10 / 0
except ZeroDivisionError:
print("Logging error...")
raise # 重新拋出同一個例外
從另一個例外拋出
try:
result = 10 / 0
except ZeroDivisionError as e:
raise ValueError("Invalid operation") from e
例外階層
Python 的例外是有階層的:
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── StopIteration
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
└── ...
# LookupError 是 IndexError 和 KeyError 的父類別
try:
my_list = [1, 2, 3]
print(my_list[10])
except LookupError:
print("Lookup failed!") # 會捕捉 IndexError 和 KeyError
實際範例
檔案處理
def read_file(filename):
try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
print(f"File '{filename}' not found")
return None
except PermissionError:
print(f"No permission to read '{filename}'")
return None
except Exception as e:
print(f"Error reading file: {e}")
return None
content = read_file("data.txt")
if content:
print(content)
使用者輸入
def get_positive_number():
while True:
try:
value = int(input("Enter a positive number: "))
if value <= 0:
raise ValueError("Number must be positive")
return value
except ValueError as e:
print(f"Invalid input: {e}")
number = get_positive_number()
print(f"You entered: {number}")
API 請求
import requests
def fetch_data(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # 如果狀態碼不是 2xx,拋出例外
return response.json()
except requests.exceptions.Timeout:
print("Request timed out")
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
assert 斷言
assert 用於測試條件,如果條件為 False 會拋出 AssertionError:
def divide(a, b):
assert b != 0, "Divisor cannot be zero"
return a / b
divide(10, 0) # AssertionError: Divisor cannot be zero
assert 主要用於開發和測試階段,在生產環境可以用 python -O 選項停用。最佳實踐
- 只捕捉你知道如何處理的例外
- 不要使用空的 except
- 使用 finally 確保資源被清理
- 提供有意義的錯誤訊息
- 記錄例外資訊以便除錯
# 不好的做法
try:
do_something()
except: # 捕捉所有例外,包括 KeyboardInterrupt
pass # 完全忽略錯誤
# 好的做法
try:
do_something()
except SpecificException as e:
logger.error(f"Operation failed: {e}")
handle_error()