URLhttps://learnscript.net/zh-hant/python/exceptions/
    複製連結移至說明  範例

    Python 例外狀況介紹,以及擲回和擷取例外狀況

    閱讀 26:00·字數 7805·發佈 
    Youtube 頻道
    訂閱 375

    先決條件

    閱讀本節的先決條件是已經掌握例外狀況的相關概念,你可以檢視程式設計教學例外狀況,例外狀況處理介紹一節來了解他們。

    Python 例外狀況

    在 Python 中,所有例外狀況(表示例外狀況的類別)都需要繼承自BaseExceptionException,這包括 Python 的內建例外狀況,以及由開發人員定義的例外狀況。當然,只有少數例外狀況直接繼承自類別BaseException,比如,可導致 Python 解譯器(可以簡單的理解為 Python 的可執行檔案或程式)結束的例外狀況SystemExit,剩余例外狀況均繼承自類別Exception或類別Exception的衍生類別。

    例外狀況基底類別 BaseException 和 Exception 之間的區別

    ExceptionBaseException類別的衍生類別,他表示不是來自於系統的非正常情況,比如,表示除數為0的例外狀況ZeroDivisionError。一般情況下,開發人員僅需要擷取從Exception類別衍生的各種例外狀況,如果將擷取的範圍擴大到BaseException,那麽可能會導致一些意想不到的問題,比如,在try…except中執行的陳述式sys.exit()無法實作結束 Python 解譯器的效果。

    在下面的範例中,由於我們將擷取範圍設定為所有例外狀況,因此sys模組的exit函式所產生的例外狀況SystemExit,並不會導致 Python 解譯器的結束,最後的print函式將被執行。

    base.py
    import sys
    
    try: sys.exit() except BaseException as e: # 例外狀況 SystemExit 將被擷取 print(f'擷取到了例外狀況 {type(e)}')
    # 下面的陳述式會被執行 print('sys.exit() 似乎沒有作用哦!')
    擷取到了例外狀況 <class 'SystemExit'>
    sys.exit() 似乎沒有作用哦!

    自訂 Python 例外狀況

    雖然 Python 已經提供了足夠多的內建例外狀況,但開發人員可能依然希望能夠自己定義他們,以完成一些特殊的業務邏輯,比如,定義一個名稱為NicknameError的例外狀況,用於說明使用者昵稱不正確的情況。這些自訂的例外狀況應該繼承自類別Exception或其衍生類別,而不是直接繼承BaseException,原因在於直接繼承自BaseException的例外狀況通常用於表示來自於系統的非正常情況。

    無論是繼承自哪個類別,你所定義的例外狀況均擁有一個具有可變參數args的建構子,這表示可以使用任意的位置參數來建立例外狀況的執行個體。

    如何為自訂例外狀況命名?

    在 Python 中,大部分內建例外狀況的名稱都以Error結尾,開發人員自訂的例外狀況應該遵守此約定。

    如何在例外狀況的標準回溯中新增註解?

    在 3.11 或更高的版本中,例外狀況擁有一個名稱為add_note的方法,可用於為例外狀況的標準回溯新增註解資訊,這些資訊可作為例外狀況的更進一步說明。

    一旦add_note方法被呼叫,名稱為__notes__的 Python 串列將被定義在例外狀況中,他包含了所有通過add_note方法新增的字串。

    類別繼承

    想要了解更多關於 Python 類別繼承的內容,你可以檢視Python 類別繼承介紹,以及實作類別繼承,多重繼承,方法覆寫一節。

    下面,我們定義了一個新的例外狀況NicknameError,並將第一和第二個位置參數規定為使用者昵稱和提示資訊,以通過add_note方法建置一個有用的註解。

    custom.py
    # 定義一個新的例外狀況 NicknameError
    class NicknameError(Exception):
    
    def __init__(self, *args): super().__init__(*args) # 將第一個和第二個位置參數作為使用者昵稱和提示資訊 (nickname, msg) = args # 為例外狀況新增註解 self.add_note(f'糟糕,昵稱「{nickname}」出問題了!{msg}') print(self.__notes__)
    # 擲回例外狀況 NicknameError raise NicknameError('Test', '不被允許的昵稱')
    ['糟糕,昵稱「Test」出問題了!不被允許的昵稱']
    NicknameError: ('Test', '不被允許的昵稱')
    糟糕,昵稱「Test」出問題了!不被允許的昵稱

    擷取處理 Python 例外狀況

    對於可能引發例外狀況的程式碼,你可以使用try…except陳述式來擷取並處理例外狀況,只需要將這些程式碼放置在try陳述式之後,並使用except陳述式來比對例外狀況即可。

    try:
        <try-block>
    except <exception-1>:
        <except-block-1>

    except <exception-N>:
        <except-block-N>

    try-block 部分

    try-block為可能會引發例外狀況的程式碼(需要使用空白字元進行縮排),當某個陳述式擲回例外狀況後,try-block中剩余的陳述式將不再被執行。

    exception 部分

    exception為用於比對特定例外狀況的運算式,需要給出一個或多個例外狀況型別,當被擲回的例外狀況或其基底類別屬於這些型別時,表示比對成功。

    except-block 部分

    except-block為處理例外狀況的程式碼(需要使用空白字元進行縮排),當被擲回的例外狀況與某一個except陳述式相符時,except陳述式對應的except-block部分的程式碼將被執行。

    最多只有一個 except 陳述式能與被擲回例外狀況相符

    如果try…except陳述式擁有多個except陳述式,那麽與if陳述式類似,他們會按照先後順序進行比對,當某一個except陳述式與被擲回的例外狀況相符時,其余的except陳述式將被忽略。

    在下面的範例中,由於例外狀況ExceptionZeroDivisionError的基底類別,因此,第一個except陳述式與被引發的例外狀況相符,第二個except陳述式將被忽略,即便他與被引發的例外狀況同樣相符。

    try.py
    try:
    	# 除數為 0 將引發例外狀況 ZeroDivisionError
    	num = 1 / 0
    except Exception:
    	# Exception 是 ZeroDivisionError 的基底類別,這裏的程式碼會被執行
    	print('比對到了例外狀況 ZeroDivisionError')
    except ZeroDivisionError:
    	print('無人問津!')
    比對到了例外狀況 ZeroDivisionError

    與所有 except 陳述式均不相符的例外狀況將被重新擲回

    如果一個例外狀況被引發,並且與所有的except陳述式均不相符,那麽該例外狀況將作為未處理的例外狀況被重新擲回,這可能導致整個程式因此結束。相反的,如果例外狀況與某個except陳述式相符,那麽他將被視為已處理,已處理的例外狀況不會被重新擲回。

    在下面的範例中,IOError並非NameError的基底類別,這導致例外狀況沒有被處理,他會被重新擲回並最終顯示了一些錯誤資訊。

    try.py
    try:
    	# 存取未定義的 undefined,將引發例外狀況 NameError
    	print(undefined)
    except IOError:
    	# IOError 不是 NameError 的基底類別,這裏的程式碼不會執行
    	print('出現了 IO 錯誤?不可能')
    NameError: name 'undefined' is not defined

    參考目前被處理的 Python 例外狀況

    except陳述式中,使用as關鍵字指定一個與被處理例外狀況繫結的識別碼(可將其簡單的視為 Python 變數),即可通過該識別碼在except陳述式相關的程式碼中存取被處理的例外狀況。比如,IOError as err(TypeError,AttributeError) as e

    此外,在 3.11 或更高版本中,通過sys模組的exception函式,同樣可在except陳述式相關的程式碼中存取目前被處理的例外狀況。

    except 陳述式會刪除與例外狀況繫結的識別碼

    如果你在某個except陳述式中使用了as關鍵字,那麽as關鍵字指定的識別碼,將在該except陳述式的相關程式碼執行完畢時被刪除。這意味著識別碼僅在except陳述式中保持其正確性,他不應該與同一命名空間的其他識別碼重複,以避免一些不必要的錯誤。

    例外狀況

    想要了解命名空間這一概念,你可以檢視程式設計教學例外狀況,例外狀況處理介紹一節。

    下面的程式碼定義了變數err,他同時是except陳述式繫結的識別碼,因此在except陳述式的相關程式碼執行完畢後,err將成為未定義的內容,使用print函式來顯示他會導致錯誤。

    一旦脫離了except陳述式,sys模組的函式exception將傳回空值None,因為此時不存在正被處理的例外狀況。

    as.py
    import sys
    # 該變數稍後會被刪除
    err = 'error'
    
    try: # 除數為 0 將引發例外狀況 ZeroDivisionError 1 / 0 except ZeroDivisionError as err: # 通過識別碼 err 和 exception 函式取得的例外狀況物件相同 print(sys.exception() == err)
    print(sys.exception()) print(err)
    True
    None

    NameError: name 'err' is not defined

    比對多個或任意型別的 Python 例外狀況

    在多數情況下,一個except陳述式只處理一種特定型別的例外狀況,但這不排除一個except陳述式處理多種特定型別的例外狀況的可能,要完成此目標,你可以在exception部分使用元組包含多個例外狀況型別,比如,(TypeError,AttributeError)

    除了比對多個例外狀況型別,except陳述式同樣可以比對任意例外狀況型別,這需要你將exception部分留空,即不書寫任何運算式。

    比對任意例外狀況型別的 except 陳述式必須是最後一個 except 陳述式

    當一個except陳述式比對任意型別的例外狀況時,該except陳述式必須是最後一個except陳述式,原因很簡單,如果他位於其他except陳述式之前,那麽一些except陳述式將失去被執行的可能。也許,Python 可以從底層打亂except陳述式的執行順序,但那樣的行為似乎並不明智。

    下面的第一個except陳述式比對例外狀況TypeErrorAttributeError,第二個except陳述式比對其他所有例外狀況。

    except.py
    import random
    import sys
    
    try: # 從多個例外狀況中隨機選擇一個並引發 errs = [TypeError, AttributeError, ZeroDivisionError, IndexError] err = errs[random.randint(0, 3)] raise err() except (TypeError, AttributeError) as e: # 檢視例外狀況的具體型別 print(f'例外狀況 {type(e)}') except: # 既不是 TypeError 也不是 AttributeError 的例外狀況 print(f'其他例外狀況 {type(sys.exception())}')
    # 輸出結果是隨機的
    其他例外狀況 <class 'IndexError'>

    處理沒有 Python 例外狀況被擲回(引發)的情況

    try…except陳述式包括的程式碼,可能會正確執行而不引發任何例外狀況,你可以使用try…except…else陳述式來處理這種情況,並將一部分tryexcept關鍵字之間的程式碼,轉移到else陳述式之後,這會使得被轉移的程式碼不再參與例外狀況的擷取,他們僅在沒有任何例外狀況被引發時執行。

    try:
        <try-block>
    except <exception-1>:
        <except-block-1>

    except <exception-N>:
        <except-block-N>
    else:
        <else-block>

    else-block 部分

    else-block為沒有例外狀況被引發時執行的程式碼(需要使用空白字元進行縮排)。

    使用 else 陳述式的先決條件是至少擁有一個 except 陳述式

    如果要使用else陳述式來處理沒有例外狀況被引發的情況,那麽在try陳述式之後,需要書寫至少一個except陳述式。

    else.py
    try:
    	import random
    	# 有一定的幾率引發例外狀況 Exception
    	if random.randint(0, 1):
    		raise Exception()
    except:
    	# 至少需要一個 except,才能書寫 else
    	print('哎呀!一個錯誤')
    else:
    	# 僅在沒有例外狀況時執行
    	print('居然沒有錯誤!')
    # 輸出結果是隨機的
    居然沒有錯誤!

    try 中的跳躍陳述式將導致 else 陳述式被忽略

    try陳述式的相關程式碼中的returncontinuebreak陳述式被執行時,else陳述式將被忽略,即便整個try陳述式的相關程式碼沒有引發任何例外狀況。

    函式run中的print函式不會被呼叫,因為try陳述式之後的return陳述式將被執行。

    else.py
    def run():
    	try:
    		# 執行 return 將導致 else 陳述式被忽略
    		return
    	except:
    		pass
    	else:
    		# 這裏的陳述式不會被執行
    		print('太好了,沒有錯誤')
    
    run()

    完成 Python 例外狀況處理的清理工作

    在處理例外狀況的過程中,一些程式碼需要始終被執行,無論是否有例外狀況被擲回,或例外狀況是否被處理。使用finally陳述式可以達成上述目標,該陳述式之後的程式碼通常與清理工作有關,比如,關閉開啟的檔案。

    try:
        <try-block>

    finally:
        <finally-block>

    finally-block 部分

    finally-block為始終被執行的程式碼(需要使用空白字元進行縮排)。

    使用 finally 陳述式後,except 陳述式將成為選擇性的

    如果書寫了finally陳述式,那麽except陳述式將不再是必須的,try陳述式之後可以沒有任何except陳述式。

    finally 中的相關程式碼將在其他跳躍陳述式執行之前執行

    tryexceptelse陳述式的相關程式碼中存在某些跳躍陳述式時,比如breakcontinuereturn,與finally陳述式相關的程式碼將在這些跳躍陳述式執行之前被執行。

    finally.py
    def show():
    	try:
    		# 傳回之前會執行 finally 中的程式碼
    		return '一條訊息'
    	finally:
    		# 在真正傳回之前,這裏的程式碼將被執行
    		print(f'在傳回之前執行!')
    
    print(show())
    在傳回之前執行!
    一條訊息

    finally 中的 return 陳述式的傳回值將取代其他傳回值

    如果finally陳述式的相關程式碼中包含了return陳述式,那麽該return陳述式所傳回的值(包括空值None),將取代tryexceptelse陳述式相關程式碼中的傳回值。

    在下面的程式碼中,呼叫div(2, 0)將引發例外狀況ZeroDivisionError,但陳述式return 0並不能讓函式的傳回值成為0,因為finally包含了自己的return陳述式,div(2, 0)的最終傳回值將是2.0

    finally.py
    def div(a, b):
    	try:
    		# 如果除數為 0,則引發例外狀況 ZeroDivisionError
    		return a / b
    	except ZeroDivisionError:
    		# 這裏的傳回值將被忽略
    		return 0
    	finally:
    		# 這裏是最終的傳回值
    		return a / (b + 1)
    
    print(div(2, 0))
    2.0

    擲回(引發)Python 例外狀況

    使用 Python 提供的raise陳述式,開發人員可以主動擲回(引發)一個例外狀況,其基本的語法形式如下。

    raise <exception>

    exception 部分

    exception是被擲回的例外狀況物件,或例外狀況型別。如果僅給出例外狀況型別,那麽將根據該型別隱含建立其對應的執行個體,比如,raise NameError的效果等同於raise NameError()

    如何使用 raise 陳述式擲回(引發)目前被處理的例外狀況?

    except陳述式的相關程式碼中,你可以將raise陳述式的exception部分留空,這會將目前被處理的例外狀況重新擲回。

    需要指出的是,以上做法的效果並不等同於呼叫exception函式的陳述式raise sys.exception(),或類似於raise err的陳述式(假設erras關鍵字繫結的識別碼),他們會展示不同的回溯(Traceback)資訊。

    在下面的範例中,陳述式raise NameError等同於陳述式raise NameError()except之後的陳述式raise,會將已經擷取到的型別為NameError的例外狀況重新擲回。

    raise.py
    try:
    	# 擲回例外狀況 NameError
    	raise NameError
    except NameError:
    	# 在比對例外狀況之後,重新將其擲回
    	raise
    NameError

    finally 中的跳躍陳述式將使未處理的例外狀況不再重新擲回

    如果finally陳述式的相關程式碼中包含了跳躍陳述式,比如breakcontinuereturn,那麽這些跳躍陳述式的執行,將導致未被except處理的例外狀況不再被重新擲回,即便這些例外狀況是通過raise陳述式主動擲回的。

    3.8 版本之前,在 Python 的finally陳述式的相關程式碼中,不能使用continue陳述式。

    在下面的程式碼中,如果函式no_exception中的return陳述式被執行,那麽未處理的型別為AttributeError的例外狀況將不再被重新擲回。

    no_exception.py
    def no_exception(r):
    	try:
    		# 引發例外狀況 AttributeError
    		raise AttributeError()
    	finally:
    		if r:
    			print('呼叫了 return 陳述式')
    			# 這裏的 return 陳述式將導致未處理的例外狀況不被擲回
    			return
    		else:
    			print('沒有呼叫 return 陳述式')
    
    # 不會顯示錯誤資訊 no_exception(True) # 會顯示錯誤資訊 no_exception(False)
    呼叫了 return 陳述式
    沒有呼叫 return 陳述式

    AttributeError

    未被處理的例外狀況需要在 finally 陳述式執行完畢後才能重新擲回

    tryexceptelse陳述式的相關程式碼引發新的不能被處理的例外狀況時,這些例外狀況不會被立即擲回,他們需要等候finally陳述式的相關程式碼的執行。

    在下面的程式碼中,型別為ValueError的例外狀況將被except陳述式處理,而except之後的raise陳述式引發了新的例外狀況,由於新的例外狀況無法得到處理,他會在finally陳述式的相關程式碼執行完畢之後,被重新擲回。

    wait.py
    try:
    	# 擲回例外狀況 ValueError
    	raise ValueError
    except ValueError:
    	# 擲回新的例外狀況 NameError,該例外狀況無法被處理
    	raise NameError
    finally:
    	print('執行 finally 中的程式碼後,才能看到錯誤資訊')
    執行 finally 中的程式碼後,才能看到錯誤資訊

    ValueError

    NameError

    將已處理的 Python 例外狀況指定為原因

    如果一個例外狀況已經被某個except陳述式處理,而該except陳述式的相關程式碼引發了新的例外狀況,那麽新的例外狀況的__context__變數,即例外狀況的上下文,將指向已被處理的例外狀況,以表示他們之間的關聯。

    在下面的範例中,except使用raise陳述式引發了新的型別為ValueError的例外狀況,他的__context__變數將指向已被except處理的型別為ZeroDivisionError的例外狀況。

    from.py
    try:
    	try:
    		# 引發例外狀況 ZeroDivisionError
    		1 / 0
    	except:
    		# 處理 ZeroDivisionError 之後,引發新的例外狀況 ValueError
    		raise ValueError
    except Exception as err:
    	print(f'{type(err)} 的 __context__ 的型別為 {type(err.__context__)}')
    <class 'ValueError'> 的 __context__ 的型別為 <class 'ZeroDivisionError'>

    如果新的例外狀況是通過raise…from陳述式引發,那麽你可以為新的例外狀況指定一個表示原因的例外狀況,這通常說明新的例外狀況是由該例外狀況導致的。

    raise…from陳述式可以在其他位置使用,但如果位於except陳述式的相關程式碼中,那麽表示原因的例外狀況一般被指定為已被except處理的例外狀況。

    raise <newexception> from <causeexception>

    newexception 部分

    newexception是被擲回的例外狀況物件,或例外狀況型別。如果僅給出例外狀況型別,那麽將根據該型別隱含建立其對應的執行個體,比如,raise RuntimeError from ValueError()的效果等同於raise RuntimeError() from ValueError()

    causeexception 部分

    causeexception是表示原因的例外狀況物件,或例外狀況型別。如果僅給出例外狀況型別,那麽將根據該型別隱含建立其對應的執行個體,比如,raise RuntimeError() from ValueError的效果等同於raise RuntimeError() from ValueError()

    回溯將優先展示表示原因的例外狀況的資訊

    一旦使用了raise…from陳述式,被擲回的例外狀況的__cause__變數將指向表示原因的例外狀況,__suppress_context__變數將被設定為True,已明確的指示應在回溯中采用__cause__而非__context__,即展示表示原因的例外狀況的資訊,而不是表示上下文的例外狀況的資訊,除非__cause__變數為None並且__suppress_context__變數為False

    在下面的範例中,我們使用raise…from陳述式將原因指定為型別為ValueError的例外狀況,而不是except陳述式已經處理的例外狀況,這導致回溯中不再展示型別為ZeroDivisionError的例外狀況的相關資訊。

    這裏需要指出,雖然程式碼將例外狀況的__suppress_context__變數設定為False,但由於其__cause__不為None,因此,回溯中不會顯示表示上下文的例外狀況的資訊。

    from.py
    try:
    	try:
    		# 引發例外狀況 ZeroDivisionError
    		1 / 0
    	except:
    		# 引發例外狀況 RuntimeError,但並未將 ZeroDivisionError 作為原因
    		raise RuntimeError from ValueError
    except Exception as err:
    	print(type(err.__context__))
    	print(type(err.__cause__))
    	print(err.__suppress_context__)
    	err.__suppress_context__ = False
    	# 重新擲回例外狀況
    	raise
    <class 'ZeroDivisionError'>
    <class 'ValueError'>
    True
    ValueError

    The above exception was the direct cause of the following exception:

    RuntimeError

    在 raise…from 陳述式中將 None 指定為原因

    raise…from陳述式的causeexception部分可以是空值None,這將使得被擲回的例外狀況的__cause__變數為None__suppress_context__變數為True,回溯中顯示的資訊被簡化。

    在下面的範例中,我們使用raise…from陳述式將原因指定為空值None,回溯中顯示了更少的資訊。

    from_none.py
    try:
    	try:
    		# 引發例外狀況 ZeroDivisionError
    		1 / 0
    	except:
    		# 引發例外狀況 RuntimeError,但將原因設定為 None
    		raise RuntimeError from None
    except Exception as err:
    	print(type(err.__context__))
    	print(err.__cause__)
    	print(err.__suppress_context__)
    	# 重新擲回例外狀況
    	raise
    <class 'ZeroDivisionError'>
    None
    True

    RuntimeError

    內容分類

    原始碼

    src/zh-hant/exceptions·codebeatme/python·GitHub