您好,登錄后才能下訂單哦!
這篇“Python錯誤異常怎么解決”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Python錯誤異常怎么解決”文章吧。
以防萬一你不熟悉異常,讓我們從一般定義開始......
exception:(計算)正常處理時發生的中斷,通常由錯誤條件引起,可由另一部分程序處理。
讓我們看一個簡單的例子:
def initiate_security_protocol(code): if code == 1: print("Returning onboard companion to home location...") if code == 712: print("Dematerializing to preset location...") code = int(input("Enter security protocol code: ")) initiate_security_protocol(code)
>>> Enter security protocol code: 712 Dematerializing to preset location... >>> Enter security protocol code: seven one two Traceback (most recent call last): File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 7, in <module> code = int(input("Enter security protocol code: ")) ValueError: invalid literal for int() with base 10: 'seven one two'
顯然,這是一個錯誤。我們不希望我們的程序因為用戶輸入了一些奇怪的東西而突然崩潰。正如下面這個笑話所說...
一名 QA 工程師走進一家酒吧。他點了一杯啤酒。他點了五瓶啤酒。他點了 -1 杯啤酒。他點了一只蜥蜴。
我們想防止奇怪的輸入。在這種情況下,只有一個重要的故障點:就是int()
函數。它期望接收可以轉換為整數的參數,如果它沒有得到它,則會拋出ValueError
異常。為了正確處理這個問題,我們將可能失敗的代碼包裝在一個try...except
塊中。
try: code = int(input("Enter security protocol code: ")) except ValueError: code = 0 initiate_security_protocol(code)
當我們再次測試我們的代碼時,我們不會遇到這種失敗錯誤。如果我們無法從用戶那里獲得我們需要的信息,我們將只需要設置code=0
。當然,我們可以重寫我們的initiate_security_protocol()
函數來處理0
不同的代碼,但是我不會在這里展示,只是為了節省時間。
注意:無論出于何種原因,作為一名多語言程序員,我經常忘記在 Python 中使用except
,而用大多數其他語言所使用的catch
語句。我已經在這篇文章中打錯了三遍(然后立即修復它)。這只是一個記憶點。值得慶幸的是,Python沒有catch
的關鍵字,因此語法錯誤會很突出。如果你會多種語言,當你感到困惑時,請不要驚慌。python里是except
,不是catch
。
在我們深入探討該try...except
語句的一些更深層次的細節之前,讓我們再次回顧一下該錯誤語句。畢竟,如果我們不討論錯誤消息,那么一篇關于錯誤處理的文章有什么用呢?在 Python 中,我們稱之為Traceback,因為它從涉及的第一行代碼到最后一行跟蹤錯誤的起源。在許多其他語言中,這將被稱為堆棧跟蹤( stack trace)。
Traceback (most recent call last): File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 7, in <module> code = int(input("Enter security protocol code: ")) ValueError: invalid literal for int() with base 10: 'seven one two'
我有從下到上閱讀這些信息的習慣,因為它可以幫助我首先獲得最重要的信息。如果你查看最后一行,你會看到ValueError
,這是已引發的特定異常。確切的細節如下;在這種情況下,無法使用 . 將字符串'seven one two'
用int()
轉換為整數。我們還了解到它正在嘗試轉換為以10 為底的整數,這在其他場景中可能是有用的信息。想象一下,例如,如果那行改成...
ValueError: invalid literal for int() with base 10: '5bff'
如果我們忘記指定以 16 為基數進行int('5bff', 16)
轉化,而不是默認值(以 10 為基數),這是完全可能的。簡而言之,你應該始終徹底閱讀并理解錯誤消息的最后一行!有太多次我看了一半的帖子,花了半個小時追錯了bug,才發現我忘記了一個參數或使用了錯誤的函數。
錯誤消息上方是錯誤來自 ( code = int(input("Enter security protocol code: "))
) 的代碼行。上面是文件的絕對路徑 ( security_protocols.py
) 和行號7
。該語句in <module>
意味著代碼在任何函數之外。在這個例子中,回調只有一步,所以讓我們看一些稍微復雜一點的東西。我已經更改并擴展了之前的代碼。
Traceback (most recent call last): File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 6, in <module> decode_message("Bad Wolf") File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 4, in decode_message initiate_security_protocol(message) File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 2, in initiate_security_protocol code = int(code) ValueError: invalid literal for int() with base 10: 'Bad Wolf'
我們遇到了與以前類似的錯誤 - 我們正在嘗試將字符串轉換為整數,但它不起作用。倒數第二行向我們展示了失敗的代碼;果然,這兒提到了int()
這一點。根據上面的行,這個有問題的代碼位于security_protocols.py
文件的第2 行initiate_security_protocol()
函數內部!我們就可以馬上找到那兒,并將其包裝在一個try...except
. 了解了為什么從下到上閱讀可以節省時間了吧?
然而,讓我們想象它并不那么簡單。也許我們沒有修改security_protocols.py
的選項,所以我們需要在執行該模塊之前防止出現問題。如果我們查看下一行,我們會看到在databank.py
第 4 行,在decode_message()
函數內部,我們調用的initiate_security_protocol()
函數有問題。是由于在databank.py
第6行被調用,這就是我們將參數傳遞"Bad Wolf"
給它的地方。
數據輸入不是問題,因為我們要解碼消息“Bad Wolf”。但是,為什么我們要將我們試圖解碼的消息傳遞給安全協議呢?也許我們需要改寫那個函數(或者除了其他更改之外?)。如你所見,Traceback對于了解錯誤的來源非常重要。養成仔細閱讀的習慣;許多有用的信息可能隱藏在意想不到的地方。
順便說一句,第一行每次都是一樣的,但是如果你忘記如何閱讀這些消息,它會非常有用。最近執行的代碼列在最后。因此,正如我之前所說,你應該從下往上閱讀它們。
“請求寬恕比獲得許可更容易。” -海軍少將格蕾絲·霍珀
這句話最初是關于主動的;如果你相信一個想法,請盡力去嘗試它,而不是等待其他人的許可來追求它。然而,在這種情況下,它很好地描述了 Python 的錯誤處理哲學:如果某些事情經常以一種或多種特定方式失敗,通常最好使用try...except
語句來處理這些情況。
這種哲學被正式命名為“請求寬恕比許可更容易”,或EAFP。
這有點抽象,所以讓我們考慮另一個例子。假設我們希望能夠在字典中查找信息。
datafile_index = { # Omitted for brevity. # Just assume there's a lot of data in here. } def get_datafile_id(subject): id = datafile_index[subject] print(f"See datafile {id}.") get_datafile_id("Clara Oswald") get_datafile_id("Ashildir")
See datafile 6035215751266852927. Traceback (most recent call last): File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 30, in <module> get_datafile_id("Ashildir") File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 26, in get_datafile_id id = datafile_index[subject] KeyError: 'Ashildir'
第一個函數調用是對的。我們在字典database_index
中搜索存在的鍵"Clara Oswald"
,因此我們返回與其關聯的值 (6035215751266852927
),并在我們格式化語句print()
中打印出該數據。但是,第二個函數調用失敗。引發異常KeyError
,因為"Ashildir"
它不是字典中的鍵。
技術說明: Python 為collections.defaultdict
這個確切問題提供了另一種解決方案;嘗試訪問不存在的鍵將使用一些默認值在字典中創建鍵/值對。但是,由于這是演示錯誤處理的示例,因此我沒有使用它。
由于不能合理地期望我們知道或記住字典中的所有鍵,尤其是在現實世界的場景中,我們需要一些方法來處理嘗試訪問不存在的鍵的常見情況。你的第一直覺可能是在嘗試訪問字典鍵之前檢查它......
def get_datafile_id(subject): if subject in datafile_index: id = datafile_index[subject] print(f"See datafile {id}.") else: print(f"Datafile not found on {subject})
在 Python 文化中,這種方法被稱為“跳前看”[LBYL]。
但這不是最有效的方法!“寬恕,而不是許可”在這里發揮作用:我們不是先測試,而是使用try...except
.
def get_datafile_id(subject): try: id = datafile_index[subject] print(f"See datafile {id}.") except KeyError: print(f"Datafile not found on {subject}")
這背后的邏輯很簡單:我們不是通過兩次獲取鍵,而是只訪問一次,并使用實際exception作為邏輯分支的手段。
在 Python 中,我們不認為異常是可以避免的。事實上,try...except
它是許多 Python 設計模式和算法的常規部分。不要害怕引發和捕獲異常!事實上,即使是鍵盤中斷也是通過KeyboardInterrupt
異常處理的。
注意: try...except
是一個強大的工具,但它并不適用于一切。例如,None
從函數返回通常被認為比引發異常更好。僅在發生最好由調用者處理的實際錯誤時拋出異常。
遲早,每個 Python 開發人員都會發現這是可行的:
try: someScaryFunction() except: print("An error occured. Moving on!")
一個單except
語句允許你在一個中捕獲所有異常。這個被稱為反面模式,這是一個非常非常糟糕的想法。總結一下……
...實際錯誤的所有上下文放在了一起拋出,永遠看不到問題跟蹤器的內部。當發生“大量”異常時,堆棧跟蹤指向發生次要錯誤的位置,而不是 try 塊內的實際失敗位置。
長話短說,你應該始終明確地捕獲特定的異常類型。任何你無法預見的失敗都可能與一些需要解決的錯誤有關;例如,當你的超級復雜搜索功能突然開始提出 anOSError
而不是預期的KeyError
或者TypeError
時。
像往常一樣,The Zen Python 對此有話要說……
錯誤永遠不應該悄無聲息地過去。
除非明確沉默。
換句話說,這不是口袋妖怪 - 你不應該抓住他們!
我不會一下子就捕捉到所有異常。那么,如何處理多個可能的故障呢?
你要知道 Python 的try...except
工具比它最初展示的要多得多。
class SonicScrewdriver: def __init__(self): self.memory = 0 def perform_division(self, lhs, rhs): try: result = float(lhs)/float(rhs) except ZeroDivisionError: print("Wibbly wobbly, timey wimey.") result = "Infinity" except (ValueError, UnicodeError): print("Oy! Don't diss the sonic!") result = "Cannot Calculate" else: self.memory = result finally: print(f"Calculation Result: {result}\n") sonic = SonicScrewdriver() sonic.perform_division(8, 4) sonic.perform_division(4, 0) sonic.perform_division(4, "zero") print(f"Memory Is: {sonic.memory}")
在我向你展示輸出之前,請仔細查看代碼。你認為這三個sonic.perform_division()
函數調用中的每一個都會打印出什么?最終存儲sonic.memory
的是什么?看看你能不能弄明白。
如果你已經有答案?讓我們看看你是否正確。
Calculation Result: 2.0 Wibbly wobbly, timey wimey. Calculation Result: Infinity Oy! Don't diss the sonic! Calculation Result: Cannot Calculate Memory Is: 2.0
你是驚訝,還是你做對了?讓我們分析一下。
try:
當然,是我們試圖運行的代碼,它可能會也可能不會引發異常。
except ZeroDivisionError:
當我們試圖除以零時發生。在這種情況下,我們說該值"Infinity"
是計算的結果,并打印出一條關于除以0的提示信息。
except (ValueError, UnicodeError):
只要引發這兩個異常之一,就會拋出異常。ValueError
是每當我們傳遞的任何參數都不能被強制轉換float()
時,就會發生這種錯誤,而UnicodeError
是如果編碼或解碼 Unicode 有問題,就會發生這種報錯。實際上,第二個只是為了說明一點。對于ValueError
所有無法將參數轉換為浮點數的可信場景,這已經足夠了。無論哪種情況,我們都將值"Cannot Calculate"
作為我們的結果,并提醒用戶不要對硬件提出不合理的要求。
這就是事情變得有趣的地方。僅在未引發異常時else:
運行。在這種情況下,如果我們有一個有效的除法計算結果,我們實際上希望將它存儲在內存中;相反,如果我們得到“無窮大”或“無法計算”作為我們的結果,我們不會存儲它。
無論如何,該finally:
部分都會運行。在這種情況下,我們打印出我們的計算結果。
順序確實很重要。我們必須遵循模式try...except...else...finally
,else
如果存在,必須在所有except
語句之后。finally
總是最后的。
最初很容易混淆else
和finally
,因此請確保你了解其中的區別。else
僅在未引發異常時運行;finally
每次運行。
你希望以下代碼實現什么?
class SonicScrewdriver: def __init__(self): self.memory = 0 def perform_division(self, lhs, rhs): try: result = float(lhs)/float(rhs) except ZeroDivisionError: print("Wibbly wobbly, timey wimey.") result = "Infinity" except (ValueError, UnicodeError): print("Oy! Don't diss the sonic!") result = "Cannot Calculate" else: self.memory = result return result finally: print(f"Calculation Result: {result}\n") result = -1 sonic = SonicScrewdriver() print(sonic.perform_division(8, 4))
下面的那return
句話else
應該是事情的結束了吧?其實,不!如果我們運行該代碼...
Calculation Result: 2.0 2.0
有兩個重要的觀察結果:
finally
正在運行,即使在我們的return
聲明之后。該函數不會像通常那樣退出。
該return
語句確實在finally
塊執行之前運行。我們知道這一點是因為結果result
輸出是2.0
,而不是我們在語句finally
中分配的-1
。
finally
每次都會運行,即使你return
在try...except
結構中的其他地方有。
但是,我也用一個os.abort()
代替測試了上面的return result
,在這種情況下,finally
塊永遠不會運行;該程序徹底中止。你可以在任何地方直接停止程序執行,Python 只會放棄它正在做的事情并退出。該規則是不變的,即使是不尋常的finally
行為。
所以,我們可以用try...except
捕獲異常. 但是如果我們只是想主動拋出一個呢?
在 Python 術語中,我們說我們引發了異常,并且與該語言中的大多數事情一樣,實現這一點很明顯:只需使用raise
關鍵字:
class Tardis: def __init__(self): pass def camouflage(self): raise NotImplementedError('Chameleon circuits are stuck.') tardis = Tardis() tardis.camouflage()
當我們執行該代碼時,我們會看到我們引發的異常。
Traceback (most recent call last): File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/tardis.py", line 10, in <module> tardis.camoflague() File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/tardis.py", line 7, in camoflague raise NotImplementedError('Chameleon circuits are stuck.') NotImplementedError: Chameleon circuits are stuck.
這樣就能知道我們在哪兒出現了異常錯誤。
注意:異常NotImplementedError是Python 中的內置異常之一,有時用于指示一個函數不應該使用,因為它還沒有完成(但總有一天會完成)。它不能與NotImplementedvalue互換。請參閱文檔以了解何時使用它們。
顯然,關鍵代碼是raise NotImplementedError('Chameleon circuits are stuck.')
. 在raise
關鍵字之后,我們給出要引發的異常對象的名稱。在大多數情況下,我們從 Exception 類創建一個新對象,從括號的使用可以看出。所有異常都接受字符串作為消息的第一個參數。一些例外接受或需要更多參數,因此請參閱官方文檔。
有時我們需要在捕捉到異常后對其進行處理。我們有一些非常簡單的方法來做到這一點。
最明顯的是從異常中打印消息。為此,我們需要能夠處理我們捕獲的異常對象。讓我們將except
語句更改為except NotImplementedError as e:
,其中e
是我們“綁定”到異常對象的名稱。然后,我們可以e
直接作為對象使用。
tardis = Tardis() try: tardis.camouflage() except NotImplementedError as e: print(e)
異常類已經定義了它的__str__()
函數來返回異常消息,所以如果我們將它轉換成一個字符串(str()
),這就是我們將得到的。你可能還記得上一篇文章print()
自動將其參數轉換為字符串。當我們運行這段代碼時,我們得到...
Chameleon circuits are stuck.
是不是很容易!
現在,如果我們想再次引發異常怎么辦?
等等,什么?我們剛剛捕獲了那個東西。為什么還要再次引發異常?
一個示例是,如果你需要在幕后進行一些清理工作,但最終仍希望調用者必須處理異常。這是一個例子......
class Byzantium: def __init__(self): self.power = 0 def gravity_field(self): if self.power <= 0: raise SystemError("Gravity Failing") def grab_handle(): pass byzantium = Byzantium() try: byzantium.gravity_field() except SystemError: grab_handle() print("Night night") raise
在上面的示例中,我們只是想捕獲一些實體 ( grab_handle()
) 并打印一條附加消息,然后讓異常繼續raise
拋出. 當我們重新引發異常時,我們說它冒泡了。
Night night Traceback (most recent call last): File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/byzantium.py", line 18, in <module> byzantium.gravity_field() File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/byzantium.py", line 8, in gravity_field raise SystemError("Gravity Failing") SystemError: Gravity Failing
注意:你可能認為我們需要說except SystemError as e:
或者說raise e
什么,但那是矯枉過正。對于冒泡的異常,我們只需要自己調用raise
。
現在,如果我們想在冒泡異常的同時添加一些額外的信息怎么辦?你的第一個猜測可能只是完全引發一個新異常,但這會帶來一些問題。為了演示,我將在執行順序中添加另一層。請注意,當我處理這個問題時SystemError
,我會提出一個新的RuntimeError
。我在第二個try...except
區塊中發現了這個新異常。
byzantium = Byzantium() def test(): try: byzantium.gravity_field() except SystemError: grab_handle() raise RuntimeError("Night night") try: test() except RuntimeError as e: print(e) print(e.__cause__)
當我們運行它時,我們得到以下輸出。
Night night None
當我們捕獲到這個新異常時,我們完全沒有關于它是什么原因的上下文。為了解決這個問題,Python 3在PEP 3134中引入了顯式異常鏈接。實現它很容易。看看我們的新函數test()
,這是我與上一個示例相比唯一更改的部分。
byzantium = Byzantium() def test(): try: byzantium.gravity_field() except SystemError as e: grab_handle() raise RuntimeError("Night night") from e try: test() except RuntimeError as e: print(e) print(e.__cause__)
你有沒有發現我在那兒做什么?在except
聲明中,我將名稱綁定e
到我們捕獲的原始異常。然后,在引發新RuntimeError
異常時,我將其鏈接到上一個異常,并使用from e
. 我們現在的輸出...
Night night Gravity Failing
當我們運行它時,我們的新異常會記住它是從哪里來的——前一個異常存儲在它的__cause__
屬性中(打印在輸出的第二行)。這對于日志記錄特別有用。
你可以使用異常類執行許多其他技巧,尤其是在引入 PEP 3134 時。像往常一樣,我建議你閱讀文檔,我在文章末尾鏈接到該文檔。
Python 有一大堆異常,它們的使用有據可查。當我為工作選擇合適的異常時,我經常參考這個異常列表。然而,有時,我們只需要更多……定制的東西。
所有錯誤類型的異常都是從Exception
類派生的,而類又是從BaseException
類派生的。這種雙重層次結構的原因是你可以捕獲所有錯誤Exceptions
,而無需對特殊的、非系統退出的異常(如KeyboardInterrupt
. 當然,這在實踐中對你來說并不重要,因為except Exception
實際上總是我之前提到的反模式的另一種形式。無論如何,不建議你直接派生自BaseException
——只要知道它存在即可。
在進行自定義異常時,你實際上可以從任何你喜歡的異常類派生。有時,最好從與你正在自定義的異常最接近的異常中獲取。但是,如果你不知所措,你可以從Exception
派生.
讓我們自定義一個,好嗎?
class SpacetimeError(Exception): def __init__(self, message): super().__init__(message) class Tardis(): def __init__(self): self._destination = "" self._timestream = [] def cloister_bell(self): print("(Ominous bell tolling)") def dematerialize(self): self._timestream.append(self._destination) print("(Nifty whirring sound)") def set_destination(self, dest): if dest in self._timestream: self.cloister_bell() self._destination = dest def engage(self): if self._destination in self._timestream: raise SpacetimeError("You should not cross your own timestream!") else: self.dematerialize() tardis = Tardis() # Should be fine tardis.set_destination("7775/349x10,012/acorn") tardis.engage() # Also fine tardis.set_destination("5136/161x298,58/delta") tardis.engage() # The TARDIS is not going to like this... tardis.set_destination("7775/349x10,012/acorn") tardis.engage()
顯然,最后一個將導致我們的SpacetimeError
異常被引發。
讓我們再看看那個異常類聲明。
class SpacetimeError(Exception): def __init__(self, message): super().__init__(message)
這實際上非常容易編寫。如果你還記得我們之前對類的探索,super().__init__()
就是在基類上調用初始化函數,Exception
在這種情況下就是這樣。我們將消息傳遞給SpacetimeError
異常構造函數,并將其交給基類初始化函數。
事實上,如果我唯一要做的就是將 傳遞message
給super()
, 類,我可以讓這更簡單:
class SpacetimeError(Exception): pass
Python 自己處理基礎異常。
這就是我們需要做的所有事情,盡管像往常一樣,我們可以用這個做更多的技巧。自定義異常不僅僅是一個漂亮的名字;我們可以使用它們來處理各種不尋常的錯誤場景,盡管這顯然超出了本指南的范圍。
以上就是關于“Python錯誤異常怎么解決”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。