最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

【翻譯】Python中的raise: 有效地在代碼中引發(fā)異常

2023-07-19 14:30 作者:月愿LW  | 我要投稿

原文鏈接:https://realpython.com/python-raise-exception/

by Leodanis Pozo Ramos Jun 19, 2023


目錄

  • 在Python中處理異常

  • 在Python中引發(fā)異常:raise語句

  • 選擇引發(fā)的異常:內(nèi)置vs自定義

    • 引發(fā)內(nèi)置的異常

    • 編寫并引發(fā)自定義的異常

  • 決定何時(shí)引發(fā)異常

  • 引發(fā)異常的實(shí)操

    • 條件性地引發(fā)異常

    • 重新引發(fā)先前的異常

    • 使用from子句鏈接異常

  • 學(xué)習(xí)引發(fā)異常的最佳做法

  • 引發(fā)異常和assert語句

  • 引發(fā)異常組

  • 結(jié)語


在你的Python學(xué)習(xí)過程中,你會(huì)遇到一些情況,需要向代碼中的問題發(fā)出信號(hào)。例如,可能文件不存在,網(wǎng)絡(luò)或數(shù)據(jù)庫連接失敗,或者你的代碼收到了無效的輸入。解決這些問題的常見方法是引發(fā)異常,通知用戶發(fā)生了錯(cuò)誤。這就是Python中的raise語句的用途。

學(xué)習(xí)raise語句可以幫助你有效處理代碼中的錯(cuò)誤和異常情況。這樣,你可以開發(fā)出更健壯的程序和更高質(zhì)量的代碼。

在本教程中,你將學(xué)習(xí)以下內(nèi)容

  • 使用raise語句在Python中引發(fā)異常

  • 在代碼中決定應(yīng)該引發(fā)哪些異常以及何時(shí)引發(fā)異常

  • 探索在Python中引發(fā)異常的常見用例

  • 在Python代碼中應(yīng)用引發(fā)異常的最佳做法

為了充分理解本教程,你應(yīng)該了解Python的基礎(chǔ)知識(shí),包括variables、data typesconditional、exceptionclasses等內(nèi)容。


在Python中處理異常

Exceptions在Python中扮演著重要的角色。它們允許你處理代碼中的errors(錯(cuò)誤)exceptional situations(異常情況)。那么什么是異常呢?異常表示一個(gè)錯(cuò)誤或指示出現(xiàn)了問題。一些編程語言(如CGo)鼓勵(lì)返回錯(cuò)誤代碼并進(jìn)行check。相反,Python鼓勵(lì)你拋出異常并進(jìn)行handle

注意:在Python中,并不是所有的異常都是錯(cuò)誤。內(nèi)置的 StopIteration 異常就是一個(gè)很好的例子。Python在內(nèi)部使用這個(gè)異常來終止iterators上的迭代。Python異常中表示錯(cuò)誤的,都有Error后綴跟在它們名字后面。

Python還有一個(gè)專門的異常類別用來向程序員發(fā)出warnings。當(dāng)你需要在程序中警示用戶某種條件時(shí),警告就派上用場了。然而,這種條件可能不足以引發(fā)異常并終止程序運(yùn)行。一個(gè)常見的警告的例子是DeprecationWarning

當(dāng)程序中出現(xiàn)了問題,Python會(huì)自動(dòng)引發(fā)異常。比如,看看當(dāng)你試圖訪問一個(gè)list對象中不存在的索引時(shí)會(huì)發(fā)生什么:

在這個(gè)例子中,你的colors列表沒有10這個(gè)索引。它的索引是從06,包含了你的7種顏色。所以,如果你試圖獲取索引為10的元素,就會(huì)得到一個(gè)IndexError異常告訴你目標(biāo)索引超出了范圍。

注意:在上面的例子中,Python自動(dòng)引發(fā)了異常,這只適用于內(nèi)置異常。而你,作為一個(gè)程序員,可以選擇使用內(nèi)置異常或定制化異常,正如你在之后的選擇引發(fā)的異常:內(nèi)置vs自定義章節(jié)會(huì)學(xué)到的。

每次引發(fā)異常都有一個(gè)traceback,也稱作stack trace, stack traceback或者backtrace,等等其他名字。一個(gè)traceback是包含追溯到當(dāng)前異常的一系列調(diào)用和操作的報(bào)告。

在Python中,大部分情況下traceback開頭是Traceback (most recent call last)。然后你就會(huì)在緊隨而來的錯(cuò)誤信息里看到真正的調(diào)用棧和異常名。

注意:自從Python 3.9引入了新的PEG parser以來,一直在不斷努力改進(jìn) tracebacks中的error messages,使其更有幫助和具體。這一努力持續(xù)取得新的成果,Python 3.12中引入了even better error messages。

異常會(huì)導(dǎo)致你的程序終止,除非你使用tryexcept代碼塊處理它們:

處理一個(gè)異常的第一步是預(yù)測可能發(fā)生的異常。如果你不這么做,就不能處理異常,程序也會(huì)崩潰。那樣的話,Python會(huì)打印出異常追溯然后你就可以研究怎么修復(fù)問題。有時(shí),你為了探索什么異常會(huì)被引發(fā)必須讓程序運(yùn)行失敗。

在上面的案例中,你事先就知道從一個(gè)列表中獲取大于它索引范圍的元素會(huì)引發(fā)IndexError異常。所以,你對于捕獲、處理這個(gè)特定的異常有所準(zhǔn)備。try代碼塊負(fù)責(zé)捕獲異常。except子句指定了你預(yù)測的異常,并且except代碼塊允許你采取相應(yīng)的措施。整個(gè)過程被稱作exception handling(異常捕獲)。

如果你的代碼在一個(gè)函數(shù)內(nèi)部引發(fā)了異常而不處理,那么異常將傳播到函數(shù)被調(diào)用的地方。如果在調(diào)用處也不處理,就會(huì)繼續(xù)傳播直達(dá)主程序。如果主程序那也不處理,程序就會(huì)終止并有一個(gè)異?;厮荨?/p>

Python里到處都是異常。幾乎每個(gè)standard library里的模塊都會(huì)使用它們(Exceptions)。Python在很多情況下都會(huì)引發(fā)異常。Python文檔聲明了這點(diǎn):

Exceptions are a means of breaking out of the normal flow of control of a code block in order to handle errors or other exceptional conditions. An exception is raised at the point where the error is detected; it may be handled by the surrounding code block or by any code block that directly or indirectly invoked the code block where the error occurred. (Source)

總結(jié),當(dāng)程序執(zhí)行過程中遇到錯(cuò)誤時(shí),Python自動(dòng)引發(fā)異常。Python也允許你根據(jù)自身需要使用raise關(guān)鍵字引發(fā)異常。這個(gè)關(guān)鍵字允許你以更加可控的方式處理你程序中的錯(cuò)誤。


在Python中引發(fā)異常:raise語句

在Python里,你可以引發(fā)內(nèi)置的或自定義的異常。當(dāng)你引發(fā)一個(gè)異常,結(jié)果就跟Python(自動(dòng))引發(fā)的是一樣的。你得到了一個(gè)異常追溯,并且你的程序會(huì)崩潰除非你按時(shí)處理異常。

注意:在Python術(shù)語里,異常被raised,而其他編程語言例如C++Java,異常被thrown。

想要自己去引發(fā)異常,你需要使用raise語句,使用如下語法:

一個(gè)不含參數(shù)的raise關(guān)鍵字會(huì)重新引發(fā)當(dāng)前激活的異常。注意你只能在含有已激活異常的except代碼塊里單獨(dú)使用raise。否則你就會(huì)得到一個(gè)RuntimeError異常:

在這個(gè)例子中,你在一個(gè)沒有激活異常的上下文環(huán)境里。因此,Python不能重新引發(fā)(reraise)一個(gè)先前的異常。改為引發(fā)一個(gè)RuntimeError異常。

在你捕獲了一個(gè)異常之后,如果需要進(jìn)行某些操作,單獨(dú)使用raise就很有用,因?yàn)榻酉聛砟銜?huì)想重新引發(fā)原始的異常。你將在重新引發(fā)一個(gè)先前的異常章節(jié)學(xué)習(xí)raise的使用案例。

raise語法中的expression對象必須返回一個(gè)繼承自BaseException的類的實(shí)例,它(BaseException)是所有內(nèi)置異常的基類。也支持返回異常類本身,這樣的話Python會(huì)為你自動(dòng)實(shí)例化該類。

注意expression可以是Python里任何返回異常類/實(shí)例的expression。例如,你用來raise的參數(shù)可以是返回一個(gè)異常的自定義函數(shù):

這里的exception_factory()函數(shù)接受一個(gè)異常類和一個(gè)錯(cuò)誤信息作為參數(shù)。然后這個(gè)函數(shù)將輸入異常實(shí)例化并把錯(cuò)誤信息作為該異常的參數(shù)。最后,它把異常實(shí)例返回給調(diào)用者。正如上面的示例,你可以使用這個(gè)函數(shù)作為raise關(guān)鍵字的一個(gè)參數(shù)。

from子句也是raise語法中的可選項(xiàng)。它允許你將異常鏈接在一起。如果你提供了from子句,那么another_expression必須是另一個(gè)異常類或?qū)嵗?。你將?span id="s0sssss00s" class="md-pair-s " style="">使用from子句鏈接異常章節(jié)學(xué)到。

這是raise的另一個(gè)例子。這次,你創(chuàng)建了一個(gè)Exception類的全新實(shí)例:

僅供舉例來說,你引發(fā)了一個(gè)Exception的實(shí)例,但是引發(fā)這種泛型的異常事實(shí)上不是最好的方式。你在之后將會(huì)學(xué)到,用戶自定義的異常應(yīng)該繼承自這個(gè)(Exception)類,雖然它們也可以繼承自其他內(nèi)置異常啦。

注意:在Python里,一般用小寫字母做異常的錯(cuò)誤信息的開頭。并且結(jié)尾也沒有句號(hào)。

為了說明,看看下列例子:

在第一個(gè)例子中,你因?yàn)樵噲D用42除以0,就得到了一個(gè)ZerorDivisionError異常。注意默認(rèn)錯(cuò)誤信息用小寫字母開頭并且結(jié)尾沒有句號(hào)。

在第二個(gè)例子中,你試圖從空列表中訪問0索引,Python引發(fā)了TypeError(疑似作者筆誤)異常。在這個(gè)例子中,錯(cuò)誤信息也遵循同樣的模式。

即使這種方式不是一個(gè)明確的慣例,你也應(yīng)該考慮在自己的代碼庫中沿用這種模式以保持一致性。


Exception這樣的異常類的構(gòu)造器接受多個(gè)positional參數(shù)或是參數(shù)的元組:

你在實(shí)例化異常時(shí)一般就使用一個(gè)參數(shù):一個(gè)提供了恰當(dāng)錯(cuò)誤信息的字符串。然而,你也可以在實(shí)例化時(shí)提供多個(gè)參數(shù)。在這個(gè)例子中,你給Exception類的構(gòu)造器提供了額外的參數(shù)。這些參數(shù)讓你能給開發(fā)者提供更多關(guān)于錯(cuò)誤如何被引發(fā)和應(yīng)該怎么修復(fù)的信息。

.args屬性使你能直接獲取傳給Exception構(gòu)造器的所有參數(shù):

在這個(gè)例子中,你使用.args屬性獲取到了參數(shù)。這個(gè)屬性保存著一個(gè)元組以便你能通過索引訪問特定的參數(shù)。

現(xiàn)在你學(xué)習(xí)了Python中關(guān)于引發(fā)異常的基礎(chǔ)知識(shí),是時(shí)候前進(jìn)啦。在自己引發(fā)異常時(shí),一個(gè)至關(guān)重要的點(diǎn)就是,你得決定在特定時(shí)刻怎樣的異常才是最恰當(dāng)?shù)摹?/p>


選擇引發(fā)的異常:內(nèi)置vs自定義

當(dāng)你的代碼中需要手動(dòng)引發(fā)異常時(shí),決定需要引發(fā)的異常是重要的一步。通常來說,你需要引發(fā)的異常得清晰傳達(dá)出正在處理的問題。在Python里,你有兩種不同的方式引發(fā)異常:

  • 內(nèi)置異常:這些異常是Python內(nèi)置的。你不用導(dǎo)入就可以在代碼里直接使用它們。

  • 用戶定義的異常:自定義異常是你在沒有內(nèi)置異常滿足需求時(shí)創(chuàng)建的。對于一個(gè)項(xiàng)目,你通常會(huì)把它們(自定義異常)放在專門的模塊里。

在接下來的章節(jié),你會(huì)找到一些關(guān)于在代碼中引發(fā)何種異常的指導(dǎo)思想。


引發(fā)內(nèi)置的異常

Python有著豐富的內(nèi)置異常集合,按照類 hierarchy 進(jìn)行組織,BaseException在頂部。BaseException最頻繁使用的子類就是Exception。

Exception類是Python異常處理框架的基礎(chǔ)。它是你能在Python里找到的大部分內(nèi)置異常的基類。也是你在自定義異常時(shí)通常會(huì)使用類。

Python有超過60種built-in exceptions。你也許在日常寫代碼時(shí)已經(jīng)見過下面這些具體的異常了:

內(nèi)置異常節(jié)選

這個(gè)表只是Python內(nèi)置異常中的一小部分。當(dāng)使用raise語句時(shí),你可以用這些以及其他所有內(nèi)置異常。

在大多數(shù)情況下,你都能為自己的特定使用情境找到合適的內(nèi)置異常。如果你找到了,使用內(nèi)置異常就比自定義的要好。比如,你正在編寫一個(gè)計(jì)算列表內(nèi)所有值的平方的函數(shù),你想確保輸入對象是列表或元組:

squared()里,你使用一個(gè) conditional 語句來檢查輸入對象是否是列表或元組。如果不是,就引發(fā)一個(gè)TypeError異常。這是一個(gè)相當(dāng)好的選擇因?yàn)槟阆氪_保輸入的類別是正確的。如果類別都錯(cuò)了,TypeError是很合理的響應(yīng)。


編寫并引發(fā)自定義的異常

如果你找不到一個(gè)內(nèi)置異常從語義上滿足你的需求,你就可以自定義一個(gè)。為了做到這點(diǎn),你必須繼承另一異常類,通常是Exception。例如,你正在編寫一個(gè)gradebook app并且需要計(jì)算學(xué)生的平均分。

你想確保所有分?jǐn)?shù)都在0100之間。為了應(yīng)對這個(gè)場景,你可以創(chuàng)建一個(gè)叫做GradeValueError的自定義異常。如果分?jǐn)?shù)不在這個(gè)范圍,就引發(fā)異常,說明這分?jǐn)?shù)無效:

在這個(gè)例子中,你先是通過繼承Exception創(chuàng)建了一個(gè)自定義異常。你不需要給自定義異常添加新功能,所以你用了pass語句在類主體中占位。這個(gè)新的異常是你的分?jǐn)?shù)項(xiàng)目中特有的。注意異常名是如何有助于傳達(dá)底層問題的。

注意:在Python里,自定義異常時(shí)使用pass語句作為類主體是常見的方式。這是因?yàn)轭惷麑τ谧远x異常通常是最重要的。

可能有的時(shí)候你也希望給自定義異常添加一些新特性,可惜這部分內(nèi)容不在本教程范圍內(nèi)。

calculate_average_grade()函數(shù)內(nèi)部,你使用for loop 來迭代輸入的分?jǐn)?shù)列表。然后你檢查當(dāng)前分?jǐn)?shù)是否在0100外。如果是這樣,你就實(shí)例化并引發(fā)自定義異常GradeValueError

你的函數(shù)以這種方式起作用:

當(dāng)有無效分?jǐn)?shù)時(shí),你的函數(shù)引發(fā)GradeValueError,這是這個(gè)項(xiàng)目特有的,針對實(shí)際錯(cuò)誤展示了清晰的信息。

有些開發(fā)者會(huì)提出異議,在這個(gè)案例里,可以直接用內(nèi)置的ValueError而不是自定義的異常,他們可能也是對的。通常來說,你會(huì)在希望點(diǎn)出項(xiàng)目特有的錯(cuò)誤或異常情景時(shí)去自定義異常。

如果你希望使用自定義異常,記住 naming conventions 也適用于它們。進(jìn)一步說,你需要給自定義異常加上Error后綴來代表錯(cuò)誤,不加就說明不是錯(cuò)誤。如果你自定義了警告,你就用Warning后綴。所有這些命名慣例都能使別的開發(fā)者更明確你的意圖。


決定何時(shí)引發(fā)異常

在有效地使用raise語句過程中,另一個(gè)重要步驟是決定何時(shí)引發(fā)異常。你得決定引發(fā)異常還是解決異常哪個(gè)才是最好的方式。通常來說,在這些情況下你就該引發(fā)異常:

  • 發(fā)出錯(cuò)誤和異常場景的信號(hào):最常見的使用raise語句的場景是在錯(cuò)誤或異常場景發(fā)生時(shí)發(fā)出信號(hào)。當(dāng)你的代碼中出現(xiàn)了錯(cuò)誤或異常場景,你可以用引發(fā)異常作為響應(yīng)。

  • 在做完額外處理后重新引發(fā)異常:一個(gè)常見的使用raise語句的場景是在做完一些操作后重新引發(fā)一個(gè)激活狀態(tài)的異常。一個(gè)好的例子是你需要先記錄錯(cuò)誤然后才能引發(fā)異常。

有的編程語言,比如C和Go,鼓勵(lì)你從函數(shù)和方法中返回錯(cuò)誤碼。然后你就可以用條件語句檢查這些錯(cuò)誤碼,相應(yīng)地來處理底層錯(cuò)誤。

比如,Go程序員很熟悉這種結(jié)構(gòu):

在這里,DoSomething()一定得返回一個(gè)結(jié)果和一個(gè)錯(cuò)誤。這個(gè)條件語句會(huì)檢查錯(cuò)誤并采取相應(yīng)舉動(dòng)。

相反,Python鼓勵(lì)你使用異常來處理錯(cuò)誤和異常場景。然后你就必須用try...except結(jié)構(gòu)來處理錯(cuò)誤。這種方式在Python代碼里相當(dāng)常見。標(biāo)準(zhǔn)庫里和Python自身就有很多例子。

一個(gè)(和上面代碼)等效的Python函數(shù)長下面這樣:

some_func()中,你使用try...except代碼塊來捕獲在do_something或任何其他該函數(shù)調(diào)用的代碼會(huì)引發(fā)的異常。error對象代表案例中的目標(biāo)異常。然后你就可以記錄錯(cuò)誤并且重新引發(fā)激活狀態(tài)的異常,也就是error

注意:那種鼓勵(lì)使用錯(cuò)誤碼和條件判斷的方式被稱作look before you leap (LBYL)。相反,鼓勵(lì)引發(fā)并處理異常的方式被稱作easier to ask forgiveness than permission (EAFP)。想了解更多,看這里LBYL vs EAFP: Preventing or Handling Errors in Python。

譯者注:LBYL:三思而后行;EAFP:先斬后奏。

在實(shí)操中,你會(huì)在錯(cuò)誤或異常情景發(fā)生時(shí)盡快引發(fā)異常。另一方面,何時(shí)捕獲并處理引發(fā)的異常又取決于你的特定場景。有時(shí),在異常最先發(fā)生的地方捕獲它比較合理。這樣,你就有足夠的上下文信息來正確地處理異常并修復(fù)它而不至于終止整個(gè)程序。

然而,如果你在寫一個(gè)庫,你一般不會(huì)有足夠的上下文信息來處理異常,所以你就讓調(diào)用者自己去處理異常。

既然現(xiàn)在你已經(jīng)明白何時(shí)引發(fā)異常了,是時(shí)候上手開始引發(fā)異常了。


引發(fā)異常的實(shí)操

在Python里,引發(fā)異常是一種處理錯(cuò)誤和異常情景的常見方式。因此,你在一些場景下會(huì)使用raise語句。

例如,你使用raise語句來引發(fā)異常作為特定情況下的響應(yīng)或者在執(zhí)行某些額外處理后重新引發(fā)激活狀態(tài)的異常。你也會(huì)使用from子句來鏈接異常以便任何人從此處或上層代碼debug時(shí)能獲取更多上下文信息。

在深入討論之前,你將學(xué)習(xí)少許異常內(nèi)部結(jié)構(gòu)的知識(shí)。異常類有一個(gè).args屬性,是一個(gè)元組,里面包含了你提供給構(gòu)造器的所有參數(shù):

.args屬性讓你能動(dòng)態(tài)地訪問在實(shí)例化時(shí)傳給構(gòu)造器的所有參數(shù)。在你引發(fā)自己的異常時(shí),這會(huì)很有幫助。

異常類還有兩個(gè)方法:

  1. .with_traceback() 允許你給異常提供一個(gè)新的追溯,返回值是更新后的異常對象。

  2. .add_note()允許你在一次異常追溯中包含one or more notes。你可以通過檢視給定異常的.__notes__特殊屬性來訪問筆記列表。

這兩個(gè)方法都讓你能夠在一個(gè)異常中提供額外信息,這能幫助用你庫的人debug他們自己的代碼。

最后,異常也有一堆特殊的,或者說雙下劃線的屬性例如.__notes__。兩個(gè)最常用的是.__traceback__.__cause__。

注意:特殊屬性一般也被稱為雙下劃線(dunder)屬性。dunder這個(gè)詞是double underscores的縮寫。

譯者注:即魔法方法/屬性。

.__traceback__屬性保存著附加在激活異常上的追溯對象:

在這個(gè)例子里,你訪問了.__traceback__屬性,它保存著一個(gè)異常追溯對象。你可以使用.with_traceback()方法和這樣的追溯對象來定制一個(gè)給定的異常。

Python在異常發(fā)生時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè)追溯對象。然后就附加在異常的.__traceback__屬性上,這(屬性)是可寫的。你可以創(chuàng)建一個(gè)異常然后使用.with_traceback()方法提供你自己的追溯。

.__cause__屬性保存著你在鏈接異常時(shí)傳給from的那個(gè)異常類的表達(dá)式。你在學(xué)習(xí)from章節(jié)時(shí)會(huì)了解更多。

現(xiàn)在既然你已經(jīng)知道了異常內(nèi)部構(gòu)建的基礎(chǔ),是時(shí)候繼續(xù)學(xué)習(xí)引發(fā)異常了。


條件性地引發(fā)異常

一種常見做法是在遇到特定場景時(shí)使用raise語句引發(fā)異常。這些場景通常和可能的錯(cuò)誤、異常情景相關(guān)。

比如,你想寫一個(gè)判斷給定數(shù)是否為質(zhì)數(shù)的函數(shù)。輸入的數(shù)肯定得是個(gè)整數(shù)。還應(yīng)該大于等于2。下面是一個(gè)通過引發(fā)異常處理這些情景的代碼實(shí)現(xiàn):

這個(gè)函數(shù)檢查了輸入的數(shù)是否是int的實(shí)例,如果不是就引發(fā)TypeError。然后檢查了輸入的數(shù)是否小于2,如果是就引發(fā)ValueError。請注意,這兩個(gè)if語句都會(huì)檢查那些如果passed silently就會(huì)觸發(fā)錯(cuò)誤或特定場景的情況。

譯者注:Python之禪里有一句:Errors should never pass silently. 錯(cuò)誤不應(yīng)該默默傳遞/通過。

然后這個(gè)函數(shù)就會(huì)在2到這個(gè)numbersquare root(平方根)之間迭代。在循環(huán)內(nèi),條件語句會(huì)檢查當(dāng)前數(shù)能否被區(qū)間內(nèi)別的數(shù)整除。如果是,就返回False因?yàn)椴皇琴|(zhì)數(shù)。否則,就返回True表明是質(zhì)數(shù)。

最后,尤其要注意,在你做任何計(jì)算之前就引發(fā)這兩類異常。通常認(rèn)為最好的做法就是像上面這個(gè)函數(shù)一樣早早地引發(fā)異常。


重新引發(fā)先前的異常

你可以使用不傳任何參數(shù)就使用raise語句來重新引發(fā)你代碼里的上次異常。典型做法是當(dāng)你需要在錯(cuò)誤發(fā)生時(shí)記錄錯(cuò)誤,就會(huì)想到這么使用raise

在這個(gè)例子中,你使用Exception來捕獲任何try代碼塊中的異常。如果任何異常發(fā)生了,你就可以把實(shí)際發(fā)生的錯(cuò)誤使用標(biāo)準(zhǔn)庫里的logging模塊記錄下來,然后重新用單獨(dú)的raise語句引發(fā)激活狀態(tài)的異常。

注意:在Python里,像上面這個(gè)例子這樣捕獲一個(gè)泛型的Exception一般被認(rèn)為很糟糕。你永遠(yuǎn)應(yīng)該努力去針對性地捕獲異常。這樣,你才能預(yù)防隱藏的未知錯(cuò)誤。

然而,在這個(gè)raise的特定案例里,你可能想在except子句里指定一個(gè)寬泛、通用的異常以便捕獲到多種不同錯(cuò)誤,記錄,然后重新引發(fā)原始異常以便上層代碼處理。

注意如果你用raise時(shí)把激活狀態(tài)異常的引用作為參數(shù)傳入,效果是相似的:

如果你把當(dāng)前錯(cuò)誤作為raise的參數(shù),追溯里就多了一塊。追溯里的第二行告訴你代碼第5行在重新引發(fā)一個(gè)異常。這是你手動(dòng)引發(fā)的異常。追溯第三行告訴你原始異常的位置,即代碼第2行。

另一種常見的重新引發(fā)異常的情景是你想把一個(gè)異常封裝成另一種,或者說捕獲一個(gè)異常然后轉(zhuǎn)換成另一種。為了說明這點(diǎn),就比如你正在寫一個(gè)數(shù)學(xué)庫,你有一些外部數(shù)學(xué)庫作為依賴項(xiàng)。每個(gè)外部庫都有它自己的異常,這樣就會(huì)讓你和用你庫的人暈頭轉(zhuǎn)向。

在這種情況下,你可以捕獲這些庫的異常,封裝成自定義的異常,然后引發(fā)。比如,下面這個(gè)函數(shù)捕獲了一個(gè)ZeroDivisionError然后封裝成自定義的MathLibraryError

在這個(gè)例子中,你捕獲了一個(gè)具體的異常,ZeroDivisionError,然后封裝成你自己的異常,MathLibraryError。這個(gè)技巧在以下情況很有用:

  • 抽象化外部異常:當(dāng)你在寫一個(gè)和多個(gè)外部組件有交互的庫時(shí),你可能想抽象外部異常并引發(fā)自定義的異常這樣你的用戶就不會(huì)依賴前者。正如上面這個(gè)例子所示。

  • 統(tǒng)一處理方式:當(dāng)你的多種異常類型都采取相同的處理方式時(shí),也許把這些異常全部捕獲并引發(fā)同一種自定義的異常更合理,然后按計(jì)劃處理。這么做可以簡化你的異常處理邏輯。

  • 增強(qiáng)捕獲異常的上下文信息:當(dāng)你處理一個(gè)一開始沒有足夠上下文或行為信息的異常時(shí),你可以添加這些特征并重新手動(dòng)引發(fā)異常。

上面這些關(guān)于重新引發(fā)異常的案例都很好,并且能使你在代碼生涯中處理異常時(shí)更方便。然而使用from子句改變異常通常是一種更好的選擇。

例如,如果你在上面的案例中用from None語法,就可以抑制ZeroDivisionError然后僅僅得到MathLibraryError的信息。在接下來的章節(jié)里,你會(huì)學(xué)習(xí)from子句的運(yùn)作方式。


使用from子句鏈接異常

raise語句有一個(gè)可選的from子句。這個(gè)子句允許你在傳參給from后把第二個(gè)異常鏈接到引發(fā)的異常上。注意如果你用了from子句,那么它的參數(shù)必須是一個(gè)返回異常類/實(shí)例的表達(dá)式。你通常會(huì)在except代碼塊中使用from來把引發(fā)的異常鏈接到激活的異常上。

如果from的參數(shù)是一個(gè)異常類的實(shí)例,那么Python就會(huì)把它附加給引發(fā)的異常的.__cause__屬性上。如果它是一個(gè)異常類,那么Python就先實(shí)例化這個(gè)類,再附加給.__cause__屬性。

from的效果是你會(huì)得到兩個(gè)異常的追溯信息:

在這個(gè)例子中,你在except子句中使用Exception來捕獲try代碼塊的任何異常。然后你從具體的異常中引發(fā)ValueError,在上面的例子中,是從ZeroDivisionError中引發(fā)。

from子句把兩個(gè)異常鏈接了起來,為用戶的debugg提供了完整的上下文信息。注意Python是如何將第一個(gè)異常呈現(xiàn)為第二個(gè)異常的直接原因。這樣,你能更好地追溯錯(cuò)誤然后修復(fù)它。

這種技巧在你處理一個(gè)可能引發(fā)多種異常的代碼時(shí)相當(dāng)便利??紤]下面這個(gè)divide()函數(shù):

這個(gè)函數(shù)在輸入?yún)?shù)不是一個(gè)數(shù)時(shí)引發(fā)TypeError。類似地,又在y參數(shù)等于0時(shí)引發(fā)ValueError因?yàn)椴荒艹?。

接下來看看from子句起到的幫助:

在第一個(gè)例子中,追溯顯示了在divide()的第二個(gè)參數(shù)為0時(shí)會(huì)發(fā)生的ValueError異常。這個(gè)追溯幫助你在代碼里追蹤實(shí)際的錯(cuò)誤。在第二個(gè)例子中,追溯直接讓你看到使用錯(cuò)誤參數(shù)類型引發(fā)的TypeError異常。

請注意,如果你不用from子句,Python也會(huì)同時(shí)引發(fā)兩個(gè)異常,只是輸出有點(diǎn)不同:

譯者注:

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

沒from: During handling of the above exception, another exception occurred:

現(xiàn)在追溯就不會(huì)點(diǎn)明第一種異常是第二種異常的根源了。

另一種使用from子句的方式是把None作為參數(shù)。使用from None可以在原始異常的追溯不是必須或不能提供信息時(shí)抑制或隱藏它。你也可以用這種語法在引發(fā)自定義異常時(shí)抑制內(nèi)置異常的追溯。

為了說明from None的用法,試想你在編寫一個(gè)調(diào)用外部REST API的包。你決定用requests庫來訪問API。然而,你不想暴露這個(gè)庫提供的異常。相反,你希望自定義異常。

注意:為了讓下面這段代碼運(yùn)行起來,你得先在Python environment 里用 pip 或類似的工具裝好requests庫。

這是如何實(shí)現(xiàn)該行為的:

call_external_api()函數(shù)接收一個(gè)URL作為參數(shù)然后向它發(fā)出GET請求。如果在API請求過程中發(fā)生了錯(cuò)誤,就引發(fā)你自定義的APIError異常。from None子句會(huì)把原始異常的追溯隱藏起來,替換為你的。

為了檢查這個(gè)函數(shù)是如何工作的,設(shè)想你在提供API endpoint時(shí)犯了個(gè)拼寫錯(cuò)誤:

在這個(gè)例子中,你拼錯(cuò)了目標(biāo)URL,。這個(gè)錯(cuò)誤引發(fā)了一個(gè)異常,由于在except子句里用了寬泛的 RequestException 異常,Python可以自動(dòng)捕捉到它。注意你是如何在追溯中移植原始異常的。相反,你只得到了APIError異常。

from None結(jié)構(gòu)在你想提供一個(gè)自定義的追溯和錯(cuò)誤信息并移植原始異常時(shí)很有用。在原始異常信息對使用者沒啥用并且你希望提供更多有用信息時(shí)(from None)就派上了用場。


學(xué)習(xí)引發(fā)異常的最佳做法

當(dāng)在Python里引發(fā)異常時(shí),你可以遵循一些讓你的代碼生涯更愉悅的做法和建議。這里有一些總結(jié):

  • 較寬泛的異常而言,更傾向于使用具體的異常:你應(yīng)該引發(fā)能滿足需求的最具體的異常。這種做法能幫助你定位和修復(fù)問題。

  • 報(bào)錯(cuò)的信息量要高,避免沒有任何信息的異常:你應(yīng)該給所有的異常都寫出描述性強(qiáng)、清晰的錯(cuò)誤信息。這種做法能給debug提供上下文環(huán)境。

  • 較自定義異常而言,更傾向于使用內(nèi)置異常:在為代碼中的每一類錯(cuò)誤編寫你自己的異常之前,應(yīng)該試著找到適當(dāng)?shù)膬?nèi)置異常。這個(gè)做法可以確保于Python生態(tài)的其余部分保持一致性。大多數(shù)有經(jīng)驗(yàn)的Python開發(fā)者都很熟悉內(nèi)置異常,所以(如果你多用內(nèi)置異常)他們就能很容易地了解并上手你的代碼。

  • 避免引發(fā)AssertionError異常:你在代碼里應(yīng)該避免引發(fā)AssertionError。因?yàn)檫@個(gè)異常是專門給assert語句用的,對其他上下文環(huán)境不適用。

  • 盡快引發(fā)異常:你應(yīng)該早早地在代碼里檢查可能的錯(cuò)誤和異常情景。這種做法確保了你的代碼不會(huì)因?yàn)橥七t錯(cuò)誤檢查帶來一些額外步驟,從而使代碼變得更加高效。這種做法符合fail-fast的設(shè)計(jì)思想。

  • 在你的代碼文檔里解釋引發(fā)的異常:你應(yīng)該清晰地列出并解釋一段代碼可能引發(fā)的所有異常。這種做法能幫助其他開發(fā)者明白他們可能遇到的異常以及怎么適當(dāng)?shù)靥幚磉@些異常。

為了說明上述建議中的部分,考慮下面這個(gè)例子,你引發(fā)了一個(gè)寬泛的異常:

在這個(gè)例子中,你使用了Exception類來點(diǎn)明一個(gè)和輸入值強(qiáng)相關(guān)的問題。在這個(gè)例子中,使用一個(gè)像ValueError這樣的更具體的異常要合適得多。改進(jìn)報(bào)錯(cuò)信息也會(huì)帶來一點(diǎn)幫助:

在這個(gè)例子中,正是異常的名字幫助你獲悉了實(shí)際的錯(cuò)誤。此外,報(bào)錯(cuò)信息也很精準(zhǔn),很有幫助。重申,不管你在用內(nèi)置還是自定義異常,報(bào)錯(cuò)的信息量都要高。

通常來說,異常里的報(bào)錯(cuò)信息應(yīng)該簡明扼要地描述出哪兒出了問題。報(bào)錯(cuò)信息應(yīng)該足夠具體,以便其他開發(fā)者可以識(shí)別、診斷、debug錯(cuò)誤。然而,它也不該顯示過多你代碼的內(nèi)部細(xì)節(jié),因?yàn)檫@可能導(dǎo)致安全漏洞。

在任何情況下,記住這些只是建議,而不是嚴(yán)格的規(guī)則。你會(huì)遇到某些希望引發(fā)并處理寬泛異常的情景。

最后,如果你在給其他開發(fā)者寫一個(gè)庫,一定要把你的函數(shù)、方法引發(fā)的異常寫進(jìn)文檔。你應(yīng)該列出你的代碼可能引發(fā)的異常,簡短地描述每種異常的意思以及調(diào)用者怎么在他們的代碼中處理異常。


引發(fā)異常和assert語句

raise語句并不是你在Python里能引發(fā)異常的唯一方式。也可以用assert語句。然而,assert語句的目的不同,它也只能引發(fā)一種錯(cuò)誤——AssertionError。

assert語句是Python里的一個(gè) debuggingtesting 工具。允許你寫出被稱作 assertionssanity checks 。你可以用這些檢查來驗(yàn)證你代碼里特定的假設(shè)是否保持真值。如果你的任何一個(gè)斷言變成了非真值,那么assert語句就會(huì)引發(fā)一個(gè)AssertionError異常。收到這個(gè)異常就說明你代碼里有bug。

注意:為了深入了解如何在你的代碼里寫斷言,看這篇文章: Python’s assert: Debug and Test Your Code Like a Pro。

你不應(yīng)該用assert語句去處理用戶的輸入或是其他種類的輸入錯(cuò)誤。為什么?因?yàn)閿嘌阅軌?,也很可能?huì)在生產(chǎn)環(huán)境中被禁用。這么一來,它們的最佳使用方式就是在開發(fā)時(shí)作為debug和測試的工具。

在Python里,assert語句有著如下語法:

在這個(gè)結(jié)構(gòu)里,expression可以是任何有效的你需要檢查 truthiness 的Python expression 或?qū)ο?。如?code>expression為非真,那么這個(gè)語句就引發(fā)一個(gè)AssertionError。這個(gè)assertion_message參數(shù)是可選的,但是鼓勵(lì)使用,因?yàn)槟茉赿ebug和測試時(shí)增加更多上下文信息。它能提供一個(gè)該語句捕獲到的問題的報(bào)錯(cuò)信息。

這里有一個(gè)案例,向你展示了怎么一個(gè)寫有著描述性錯(cuò)誤信息的assert語句:

這個(gè)斷言里的錯(cuò)誤信息清晰地傳達(dá)了是什么導(dǎo)致條件判斷失敗。在這個(gè)例子中,條件判斷失敗了,所以assert語句引發(fā)了一個(gè)AssertionError作為響應(yīng)。再次,你應(yīng)該用別的什么而不是assert語句來檢查輸入,因?yàn)樯a(chǎn)環(huán)境里斷言會(huì)被禁用,這樣輸入就會(huì)跳過驗(yàn)證。

另一個(gè)很重要的事是,你的代碼里不應(yīng)該顯式地用raise語句引發(fā)AssertionError。這個(gè)異常只該在你開發(fā)過程中測試、debug代碼時(shí)作為斷言失敗的結(jié)果而出現(xiàn)。


引發(fā)異常組

如果你在用Python 3.11 或更高版本,那么你可以選擇使用新的 ExceptionGroup 類和相關(guān)聯(lián)的except*語法。這種新的Python特性在你需要并行處理多個(gè)錯(cuò)誤時(shí)很有用。例如,你可能會(huì)在一個(gè)asynchronous 程序具有多個(gè)并行的、可能同時(shí)失敗的任務(wù)時(shí)求助于它們(異常組)。但通常來說,你不會(huì)經(jīng)常用上ExceptionGroup

有別于常見的錯(cuò)誤信息,ExceptionGroup構(gòu)造器接收一個(gè)額外的由非空的異常列表組成的參數(shù)。這里有一個(gè)簡化例子展示了如何引發(fā)異常組以及它的追溯長什么樣:

正如你在Python里引發(fā)的任何其他異常那樣,你引發(fā)了一個(gè)ExceptionGroup。然而,異常組的追溯和常規(guī)異常的追溯相當(dāng)不同。你會(huì)獲取到異常組的信息和組內(nèi)異常的信息。

一旦你把多個(gè)異常封裝成一個(gè)異常組,就可以使用except*語法捕獲到它們,像下面這樣:

注意這個(gè)結(jié)構(gòu)與多個(gè)的捕獲不同異常except子句或是單個(gè)的捕獲多個(gè)異常的except子句表現(xiàn)都不同。在后一種情況里,代碼將捕獲到第一個(gè)出現(xiàn)的異常。而有了新的語法,你的代碼就會(huì)引發(fā)所有異常,當(dāng)然也能全部捕獲到。

最終,當(dāng)你引發(fā)ExceptionGroup,Python會(huì)把它視作普通的異常因?yàn)樗褪?code>Exception的子類。例如,如果你把except子句里的星號(hào)去掉了,Python就捕獲不到列表里的任何一個(gè)異常了:

在這段新的代碼里,你把except子句的星號(hào)去掉了。那么,你的代碼就不會(huì)捕獲異常組里的任何一個(gè)單獨(dú)的異常。這意思就是如果你想捕獲任何異常組里的子異常,你就必須使用except*語法。

然而,如果你想捕獲異常組本身,也可以直接使用except(不帶星號(hào)):

在常規(guī)except子句的上下文里,Python像捕獲其他任何異常那樣捕獲了ExceptionGroup。它捕獲了組,運(yùn)行了處理部分的代碼。


結(jié)語

現(xiàn)在你對于在Python里用raise語句引發(fā)異常有了一個(gè)扎實(shí)的理解。你也學(xué)到了何時(shí)在代碼里引發(fā)異常以及根據(jù)正在處理的錯(cuò)誤或問題決定應(yīng)該引發(fā)的異常。此外,你也深入了解了一些能提高你處理問題/異常的代碼水平的最佳做法和建議。

在這個(gè)教程里,你學(xué)會(huì)了:

  • 使用raise語句在Python中引發(fā)異常

  • 在代碼中決定應(yīng)該引發(fā)哪些異常以及何時(shí)引發(fā)異常

  • 探索在Python中引發(fā)異常的常見用例

  • 在Python代碼中應(yīng)用引發(fā)異常的最佳做法

你已經(jīng)具備了在代碼里有效處理錯(cuò)誤和異常情景所需的知識(shí)和技能。

有了這些新技能,你就能以可靠的、可維護(hù)的代碼優(yōu)雅地處理錯(cuò)誤和異常情景??傮w而言,有效地處理異常是一項(xiàng)在Python編程中至關(guān)重要的技能,所以不斷實(shí)踐并進(jìn)一步完善它,以成為一個(gè)更好的開發(fā)者!


翻譯說明:

  1. 翻譯原稿為markdown文檔,復(fù)制到B站后超鏈接全部失效,所以會(huì)較突兀地出現(xiàn)許多英文詞、短句,這些原本都是超鏈接。

  2. 除了1.里的超鏈接,引用、專有詞匯等一般不予翻譯,除非特別影響理解。


【翻譯】Python中的raise: 有效地在代碼中引發(fā)異常的評論 (共 條)

分享到微博請遵守國家法律
怀安县| 永康市| 班戈县| 蕲春县| 扎赉特旗| 威信县| 昭觉县| 彭阳县| 随州市| 家居| 房产| 贡嘎县| 铜山县| 新郑市| 印江| 井研县| 勐海县| 长沙县| 湘阴县| 咸丰县| 菏泽市| 桂阳县| 舞阳县| 靖安县| 尖扎县| 汝州市| 奉贤区| 洪雅县| 平武县| 东丰县| 武定县| 丰都县| 广宗县| 宜州市| 正镶白旗| 中方县| 余庆县| 榆中县| 甘谷县| 肇东市| 阿坝县|