python學習筆記:第九章異常

2023-07-13 15:00:57

1.1 異常是什麼

python使用異常物件來表示異常狀態,並在遇到錯誤時引發異常。異常物件未被處理,程式將終止並顯示一條錯誤資訊。

我們可以通過各種方法引發和捕獲錯誤,並採取對應措施。

1.2 將「錯誤」變成異常

自主地引發異常

1.2.1 raise語句

我們通過預測異常可能發生的位置,通過raise語句主動丟擲異常,用except語句來接收前面出現的異常,並作出對應的操作

def divide(x, y):
    if y == 0:
    	# 引出異常
        raise ZeroDivisionError("Division by zero!")
    else:
        return x / y

# 測試
try:
    result = divide(6, 0)
# 接收異常
except ZeroDivisionError as ex:
    print(f"Error: {str(ex)}")
else:
    print(result)
> Error: Division by zero!

一些常見的內建異常類

Exception   # 幾乎所有異常類均由這個派生而來
AttributeError	試圖存取一個物件沒有的樹形,比如foo.x,但是foo沒有屬性x
IOError	輸入/輸出異常;基本上是無法開啟檔案
ImportError	無法引入模組或包;基本上是路徑問題或名稱錯誤
IndentationError	語法錯誤(的子類) ;程式碼沒有正確對齊
IndexError	下標索引超出序列邊界,比如當x只有三個元素,卻試圖存取x[5]
KeyError	試圖存取字典裡不存在的鍵
KeyboardInterrupt	Ctrl+C被按下
NameError	使用一個還未被賦予物件的變數
SyntaxError	Python程式碼非法,程式碼不能編譯(個人認為這是語法錯誤,寫錯了)
TypeError	傳入物件型別與要求的不符合

1.3 自定義的異常類

首先,要直接或間接地繼承Exception

格式

class 異常類名(Exception):
	pass

利用自定義的異常類對1.2中的例子進行改進

class MyException(Exception):  # 自定義異常類
    pass

def divide(x, y):
    if y == 0:
        raise MyException("Division by zero!")
    else:
        return x / y

# 測試
try:
    result = divide(6, 0)
except MyException as ex:
    print(f"Error: {str(ex)}")
else:
    print(result) 

1.3.1一個具體使用場景

檢查使用者輸入的郵件格式,不對則報錯

class InvalidEmailException(Exception):
    def __init__(self, email):
        self.email = email
        self.message = f"{email} is not a valid email address. Please try again."
        super().__init__(self.message)

def send_email(to, subject, body):
    if "@" not in to:
        raise InvalidEmailException(to)  # 丟擲異常
    print(f"Email sent to {to} with subject '{subject}' and body '{body}'.")

# 測試
try:
    send_email("invalid-email", "Test email", "This is a test email.")
except InvalidEmailException as ex:
    print(f"Error: {ex.message}")

第五行super().__init__(self.message) 呼叫基礎類別 Exception 的建構函式來初始化 self.message

Exception的建構函式:

class Exception(BaseException):
    def __init__(self, *args: object) -> None:
        """
        Initialize self.  See help(type(self)) for accurate signature.
        """
        pass

建構函式接受可變長度的引數 *args,但是它並沒有執行任何實際操作,只是一個空的 pass 語句。這意味著,我們可以在自定義異常類別建構函式中呼叫父類別 Exception 的建構函式,同時傳遞自定義的錯誤訊息作為引數。

關於第五行的作用:

  • 第5行的作用只是使用基礎類別 Exception 的建構函式,來完成可能會影響異常的某些其他行為,如設定異常的堆疊跟蹤資訊等。這些資訊可以幫助程式設計師更輕鬆地找到程式碼中發生異常的位置。

    因此,雖然第5行實際上不是非常必要,但它是一個良好的實踐,可以幫助進一步豐富異常資訊。

  • 個人認為,可以通過重寫Exception的建構函式

  • 猜想:可能會定義多個自定義類,通過定義Exception引數的方式,進行類之間的異常資訊傳遞

1.3.2 同時監測多種異常

將異常型別以元組形式展現(沒有采用raise,等待程式丟擲異常並接收)

class CustomException(Exception):
    def __init__(self, message):
        super().__init__(message) # 與上個程式第五行同理

# 若出現異常,則要求使用者重新輸入
while True:
    try:
        # 獲取使用者輸入的除數和被除數
        divisor = int(input("Enter the divisor: "))
        dividend = int(input("Enter the dividend: "))
        result = dividend / divisor
    # 通過元組進行多種異常監測
    except (ZeroDivisionError, TypeError, ValueError) as ex:
        # 捕獲多種異常
        print(ex)
    else:
        print(f"The result of division is: {result}")
        break  # 成功即跳出迴圈

執行結果

Enter the divisor: 0
Enter the dividend: 1
division by zero
Enter the divisor: we
invalid literal for int() with base 10: 'we'
Enter the divisor: 2
Enter the dividend: 1
The result of division is: 0.5

1.3.3 一網打盡的異常和else

同時監測多個異常可能不夠,可以一網打盡

# 若出現異常,則要求使用者重新輸入
while True:
    try:
        # 獲取使用者輸入的除數和被除數
        divisor = int(input("Enter the divisor: "))
        dividend = int(input("Enter the dividend: "))
        result = dividend / divisor
    # 通過元組進行多種異常監測
    except Exception as ex:   
    #對所有Exception的子類異常進行監測,只有發生了對應的子類異常,才會被捕獲
        # 捕獲多種異常
        print(ex)
    else:  # 通過else語句實現迴圈,這裡是except語句的else, 當不執行except語句時,執行else
        print(f"The result of division is: {result}")
        break  # 成功即跳出迴圈

執行結果

Enter the divisor: er
invalid literal for int() with base 10: 'er'
Enter the divisor: 0
Enter the dividend: 1
division by zero
Enter the divisor: 1
Enter the dividend: 2
The result of division is: 2.0

tip:

在編寫程式碼時,最好不要捕獲所有異常型別。我們應該儘可能地特定地捕獲那些預期的、已知的異常型別,並將其他異常型別傳遞給更高層的例外處理機制進行處理。這樣可以更加有效地偵錯和解決問題,而且程式碼更加可讀和可維護。

1.3.4 最後的finally

無論是否發生異常,finally語句均會執行。多用於執行清理工作。

如:關閉檔案,關閉網路通訊端等

try:
    1 / 2
except NameError:
    print("Unknown wariable")
else:
    print('That went well')
finally:
    print('Cleaning up.')

執行結果

That went well
Cleaning up.

1.3.5 異常的傳遞

如果不處理常式中引發的異常,它將向上傳播到呼叫函數中,直到主程式,若主程式中還是不能處理異常,程式將通知並顯示站跟蹤資訊。

def faulty():
    raise Exception("wrong")

def ig_exception():
    faulty()

def hl_exception():
    try:
        faulty()
    except:
        print('Exception handled')
ig_exception()

執行結果:列印了棧跟蹤資訊和異常資訊

Traceback (most recent call last):
  File "d:\M\github\Python\Demo\t12.py", line 12, in <module>
    ig_exception()
  File "d:\M\github\Python\Demo\t12.py", line 5, in ig_exception
    faulty()
  File "d:\M\github\Python\Demo\t12.py", line 2, in faulty
    raise Exception("wrong")
Exception: wrong

若是隻呼叫hl_exception() 異常會被處理,程式不會終止

hl_exception()
print("hello")  # 列印出結果,說明程式未終止,仍在執行
> Exception handled
> hello

1.4 異常之禪

  • 除了使用try/except語句來處理異常外,還可以使用if/else語句,只是不推薦這樣做
  • 可以檢查物件是否包含特定的屬性
try:
	obj.write
except AttributeError:
	print('The object is not worteable')
else:
	print('The object is writeable')
  • 不那麼異常的時候,可以發出警告,由模組warning 中的函數warn提供
from warnings import warn
warn('got a bad feeling.')
print('hello')   # 可以列印,說明程式仍在執行
> 
d:\M\github\Python\Demo\t12.py:2: UserWarning: got a bad feeling.
  warn('got a bad feeling.')
hello