<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 第八章 異常 > 來源:http://www.cnblogs.com/Marlowes/p/5428641.html > 作者:Marlowes 在編寫程序的時候,程序員通常需要辨別事件的正常過程和異常(非正常)的情況。這類異常事件可能是錯誤(比如試圖除以`0`),或者是不希望經常發生的事情。為了能夠處理這些異常事件,可以在所有可能發生這類事件的地方都使用條件語句(比如讓程序檢查除法的分母是否為零)。但是,這么做可能不僅會沒效率和不靈活,而且還會讓程序難以閱讀。你可能會想直接忽略這些異常事件,期望它們永不發生,但Python的異常對象提供了非常強大的替代解決方案。 本章介紹如何創建和引發自定義的異常,以及處理異常的各種方法。 ## 8.1 什么是異常 Python用_異常對象_(exception object)來表示異常情況。遇到錯誤后,會引發異常。如果異常對象并未被處理或捕捉,程序就會用所謂的_回溯_(traceback, 一種錯誤信息)終止執行: ``` >>> 1 / 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero ``` 如果這些錯誤信息就是異常的全部功能,那么它也就不必存在了。事實上,每個異常都是一些類(本例中是`ZeroDivisionError`)的實例,這些實例可以被引發,并且可以用很多種方法進行捕捉,使得程序可以捉住錯誤并且對其進行處理,而不是讓整個程序失效。 ## 8.2 按自己的方式出錯 異常可以在某些東西出錯的時候自動引發。在學習如何處理異常之前,先看一下自己如何引發異常,以及創建自己的異常類型。 ### 8.2.1 `raise`語句 為了引發異常,可以使用一個類(應該是`Exception`的子類)或者實例參數調用`raise`語句。使用類時,程序會自動創建類的一個實例。下面是一些簡單的例子,使用了內建的`Exception`的異常類: ``` >>> raise Exception Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception >>> raise Exception("hyperdrive overload") Traceback (most recent call last): File "<stdin>", line 1, in <module> \ Exception: hyperdrive overload ``` 第一個例子raise Exception引發了一個沒有任何有關錯誤信息的普通異常。后一個例子中,則添加了錯誤信息hyperdrive overload。 內建的異常類有很多。Python庫參考手冊的Built-in Exceptions一節中有關與它們的描述。用交互式解釋器也可以分析它們,這些內建異常都可以在`exceptions`模塊(和內建的命名空間)中找到。可以使用`dir`函數列出模塊內容,這部分會在第十章中講到: ``` >>> import exceptions >>> dir(exceptions) ['ArithmeticError', 'AssertionError', 'AttributeError', ...] ``` 讀者的解釋器中,這個名單可能要長得多——出于對易讀性的考慮,這里刪除了大部分名字,所有這些異常都可以用在`raise`語句中: ``` >>> raise ArithmeticError Traceback (most recent call last): File "<stdin>", line 1, in <module> ArithmeticError ``` 表8-1描述了一些最重要的內建異常類: 表8-1 一些內建異常類 ``` Exception            所有異常的基類 AttributeError          特性引用或賦值失敗時引發 IOError             試圖打開不存在文件(包括其他情況)時引發 IndexError ? ? ? ? ? ? ? ? ? ? ? 在使用序列中不存在的索引時引發 KeyError            ?在使用映射中不存在的鍵時引發 NameError           ? 在找不到名字(變量)時引發 SyntaxError           在代碼為錯誤形式時引發 TypeError            在內建操作或者函數應用于錯誤類型的對象時引發 ValueError            在內建操作或者函數應用于正確類型的對象,但是該對象使用不合適的值時引發 ZeroDivisionError        在除法或者模除操作的第二個參數為0時引發 ``` ### 8.2.2 自定義異常類 盡管內建的異常類已經包括了大部分的情況,而且對于很多要求都已經足夠了,但是有些時候還是需要創建自己的異常類。比如在超光速推進裝置過載(hyperdrive overload)的例子中,如果能有個具體的`HyperDriveError`類來表示超光速推進裝置的錯誤狀況是不是更自然一些?錯誤信息是足夠了,但是會在8.3節中看到,可以根據異常所在的類,選擇性地處理當前類型的異常。所以如果想要使用特殊的錯誤處理代碼處理超光速推進裝置的錯誤,那么就需要一個獨立于`exceptions`模塊的異常類。 那么如何創建自己的異常類呢?就像其他類一樣,只是要確保從`Exception`類繼承(不管是間接還是直接,也就是說繼承其他的內建異常類也是可以的)。那么編寫一個自定義異常類基本上就像下面這樣: ``` class SomeCustomException(Exception): pass ``` 還不能做太多事,對吧?(如果你愿意,也可以向你的異常類中增加方法) ## 8.3 捕捉異常 前面曾經提到過,關于異常的最有意思的地方就是可以處理它們(通常叫做誘捕或者捕捉異常)。這個功能可以使用`try/except`語句來實現。假設創建了一個讓用戶輸入兩個數,然后進行相除的程序,像下面這樣: ``` x = input("Enter the first number: ") y = input("Enter the second number: ") print x / y ``` 程序工作正常,假如用戶輸入0作為第二個數 ``` Enter the first number: 10 Enter the second number: 0 Traceback (most recent call last): File "/home/marlowes/MyPython/My_Exception.py", line 6, in <module> print x / y ZeroDivisionError: integer division or modulo by zero ``` 為了捕捉異常并且做出一些錯誤處理(本例中只是輸出一些更友好的錯誤信息),可以這樣重寫程序: ``` try: x = input("Enter the first number: ") y = input("Enter the second number: ") print x / y except ZeroDivisionError: print "The second number can't be zero!" ``` 看起來用`if`語句檢查`y`值會更簡單一些,本例中這樣做的確很好。但是如果需要給程序加入更多除法,那么就得給每個除法加個if語句。而且使用`try/except`的話只需要一個錯誤處理器。 _注:如果沒有捕捉異常,它就會被“傳播”到調用的函數中。如果在那里依然沒有捕獲,這些異常就會“浮”到程序的最頂層,也就是說你可以捕捉到在其他人的函數中所引發的異常。有關這方面的更多信息,請參見8.10節。_ **看,沒參數** 如果捕捉到了異常,但是又想重新引發它(也就是說要傳遞異常,不進行處理),那么可以調用不帶參數的`raise`(還能在捕捉到異常時顯式地提供具體異常,在8.6節會對此進行解釋)。 舉個例子吧,看看這么做多么有用:考慮一下一個能“屏蔽”`ZeroDivisionError`(除零錯誤)的計算器類。如果這個行為被激活,那么計算器就打印錯誤信息,而不是讓異常傳播。如果在與用戶交互的過程中使用,那么這就有用了,但是如果是在程序內部使用,引發異常會更好些。因此“屏蔽”機制就可以關掉了,下面是這樣一個類的代碼: ``` class MuffledCalculator(): muffled = False def calc(self, expr): try: return eval(expr) except ZeroDivisionError: if self.muffled: print "Division by zero is illegal" else: raise ``` _注:如果除零行為發生而屏蔽機制被打開,那么`calc`方法會(隱式地)返回`None`。換句話說,如果打開了屏蔽機制,那么就不應該依賴返回值。_ 下面是這個類的用法示例,分別打開和關閉了屏蔽: ``` >>> calculator = MuffledCalculator() >>> calculator.calc("10 / 2") 5 >>> calculator.calc("10 / 0") Traceback (most recent call last): File "/home/marlowes/MyPython/My_Exception.py", line 28, in <module> calculator.calc("10 / 0") File "/home/marlowes/MyPython/My_Exception.py", line 19, in calc return eval(expr) File "<string>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero >>> calculator.muffled = True >>> calculator.calc("10 / 0") Division by zero is illegal ``` 當計算器沒有打開屏蔽機制時,`ZeroDivisionError`被捕捉但已傳遞了。 ## 8.4 不止一個`except`子句 如果運行上一節的程序并且在提示符后面輸入非數字類型的值,就會產生另一個異常: ``` Enter the first number: 10 Enter the second number: "Hello, world!" Traceback (most recent call last): File "/home/marlowes/MyPython/My_Exception.py", line 8, in <module> print x / y TypeError: unsupported operand type(s) for /: 'int' and 'str' ``` 因為`except`子句只尋找`ZeroDivisionError`異常,這次的錯誤就溜過了檢查并導致程序終止。為了捕捉這個異常,可以直接在同一個`try/except`語句后面加上另一個`except`子句: ``` try: x = input("Enter the first number: ") y = input("Enter the second number: ") print x / y except ZeroDivisionError: print "The second number can't be zero!" except TypeError: print "That wasn't a number, was it?" ``` 這次用`if`語句實現可就復雜了。怎么檢查一個值是否能被用在除法中?方法很多,但是目前最好的方式是直接將值用來除一下看看是否奏效。 還應該注意到,異常處理并不會搞亂原來的代碼,而增加一大堆`if`語句檢查可能的錯誤情況會讓代碼相當難讀。 ## 8.5 用一個塊捕捉兩個異常 如果需要用一個塊捕捉多個類型異常,那么可以將它們作為元組列出,像下面這樣: ``` try: x = input("Enter the first number: ") y = input("Enter the second number: ") print x / y except (ZeroDivisionError, TypeError, NameError): print "Your numbers were bogus..." ``` 上面的代碼中,如果用戶輸入字符串或者其他類型的值,而不是數字,或者第2個數為0,都會打印同樣的錯誤信息。當然,只打印一個錯誤信息并沒有什么幫助。另外一個方法就是繼續要求輸入數字直到可以進行除法運算為止。8.8節中會介紹如何實現這一功能。 注意,`except`子句中異常對象外面的圓括號很重要。忽略它們是一種常見的錯誤,那樣你會得不到想要的結果。關于這方面的解釋,請參見8.6節。 ## 8.6 捕捉對象 如果希望在`except`子句中訪問異常對象本身,可以使用兩個參數(注意,就算要捕捉到多個異常,也只需向`except`子句提供一個參數——一個元組)。比如,如果想讓程序繼續運行,但是又因為某種原因想記錄下錯誤(比如只是打印給用戶看),這個功能就很有用。下面的示例程序會打印異常(如果發生的話),但是程序會繼續運行: ``` try: x = input("Enter the first number: ") y = input("Enter the second number: ") print x / y except (ZeroDivisionError, TypeError), e: print e ``` (在這個小程序中,`except`子句再次捕捉了兩種異常,但是因為你可以顯式地捕捉對象本身,所以異常可以打印出來,用戶就能看到發生什么(8.8節會介紹一個更有用的方法)。——譯者注) _注:在Python3.0中,`except`子句會被寫作`except (ZeroDivisionError, TypeError) as e`。_ ## 8.7 真正的捕捉 就算程序能處理好幾種類型的異常,但是有些異常還會從眼皮地下溜走。比如還用那個除法程序來舉例,在提示符下面直接按回車,不輸入任何東西,會的到一個類似下面這樣的錯誤信息(_棧跟蹤_): ``` Traceback (most recent call last): File "/home/marlowes/MyPython/My_Exception.py", line 33, in <module> x = input("Enter the first number: ") File "<string>", line 0 ^ SyntaxError: unexpected EOF while parsing ``` 這個異常逃過了`try/except`語句的檢查,這很正常。程序員無法預測會發生什么,也不能對其進行準備。在這些情況下,與其用那些并非捕捉這些異常的`try/except`語句隱藏異常,還不如讓程序立刻崩潰。 但是如果真的想用一段代碼捕捉所有異常,那么可以在except子句中忽略所有的異常類: ``` try: x = input("Enter the first number: ") y = input("Enter the second number: ") print x / y except: print "Something wrong happened..." ``` 現在可以做任何事情了: ``` Enter the first number: "This" is *completely* illegal 123 Something wrong happened... ``` _警告:像這樣捕捉所有異常是危險的,因為它會隱藏所有程序員未想到并且未做好準備處理的錯誤。它同樣會捕捉用戶終止執行的Ctrl+C企圖,以及用`sys.exit`函數終止程序的企圖,等等。這時使用`except Exception, e`會更好些,或者對異常對象`e`進行一些檢查。_ ## 8.8 萬事大吉 有些情況中,沒有壞事發生時執行一段代碼是很有用的;可以像對條件和循環語句那樣,給`try/except`語句加個`else`子句: ``` try: print "A simple task" except: print "What? Something went wrong?" else: print "Ah... It went as planned." ``` 運行之后會的到如下輸出: ``` A simple task Ah... It went as planned. ``` 使用`else`子句可以實現在8.5節中提到的循環: ``` while True: try: x = input("Enter the first number: ") y = input("Enter the second number: ") value = x / y print "x / y is", value except: print "Invalid input. Please try again." else: break ``` 這里的循環只有在沒有異常引發的情況下才會退出(由`else`子句中的`break`語句退出)。換句話說,只要有錯誤發生,程序會不斷要求重新輸入。下面是一個例子的運行情況: ``` Enter the first number: 1 Enter the second number: 0 Invalid input. Please try again. Enter the first number: "foo" Enter the second number: "bar" Invalid input. Please try again. Enter the first number: baz Invalid input. Please try again. Enter the first number: 10 Enter the second number: 2 x / y is 5 ``` 之前提到過,可以使用空的`except`子句來捕捉所有`Exception`類的異常(也會捕捉其所有子類的異常)。百分之百捕捉到所有的異常是不可能的,因為`try/except`語句中的代碼可能會出現問題,比如使用舊風格的字符串異常或者自定義的異常類不是`Exception`類的子類。不過如果需要使用`except Exception`的話,可以使用8.6節中的技巧在除法程序中打印更加有用的錯誤信息: ``` while True: try: x = input("Enter the first number: ") y = input("Enter the second number: ") value = x / y print "x / y is", value except Exception, e: print "Invalid input:", e print "Please try again" else: break ``` 下面是示例運行: ``` Enter the first number: 1 Enter the second number: 0 Invalid input: integer division or modulo by zero Please try again Enter the first number: "x" Enter the second number: "y" Invalid input: unsupported operand type(s) for /: 'str' and 'str' Please try again Enter the first number: quuux Invalid input: name 'quuux' is not defined Please try again Enter the first number: 10 Enter the second number: 2 x / y is 5 ``` ## 8.9 最后······ 最后,是`finally`子句。它可以用來在可能的異常后進行清理。它和`try`子句聯合使用: ``` x = None try: x = 1 / 0 finally: print "Cleaning up..." del x ``` 上面的代碼中,`finally`子句肯定會被執行,不管`try`子句中是否發生異常(在`try`子句之前初始化`x`的原因是如果不這樣做,由于`ZeroDivisionError`的存在,`x`就永遠不會被賦值。這樣就會導致在`finally`子句中使用`del`刪除它的時候產生異常,而且這個異常是無法捕捉的)。 運行這段代碼,在程序崩潰之前,對于變量`x`的清理就完成了: ``` Cleaning up... File "/home/marlowes/MyPython/My_Exception.py", line 36, in <module> x = 1 / 0 ZeroDivisionError: integer division or modulo by zero ``` _注:在Python2.5之前的版本內,`finally`子句需要獨立使用,而不能作為`try`語句的`except`子句使用。如果都要使用的話,那么需要兩條語句。但在Python2.5及其之后的版本中,可以盡情地組合這些子句。_ ## 8.10 異常和函數 異常和函數能很自然地一起工作。如果異常在函數內引發而不被處理,它就會傳播至(浮到)函數調用的地方。如果在那里也沒有處理異常,它就會繼續傳播,一直到達主程序(全局作用域)。如果那里沒有異常處理程序,程序會帶著棧跟蹤中止。看個例子: ``` >>> def faulty(): ... raise Exception("Something is wrong") ... >>> def ignore_exception(): ... faulty() ... >>> def handle_exception(): ... try: ... faulty() ... except: ... print "Exception handled" ... >>> ignore_exception() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in ignore_exception File "<stdin>", line 2, in faulty Exception: Something is wrong >>> handle_exception() Exception handled ``` 可以看到,`faulty`中產生的異常通過`faulty`和`ignore_exception`傳播,最終導致了棧跟蹤。同樣地,它也傳播到了`handle_exception`,但在這個函數中被`try/except`語句處理。 ## 8.11 異常之禪 異常處理并不是很復雜。如果知道某段代碼可能會導致某種異常,而又不希望程序以堆棧跟蹤的形式終止,那么就根據需要添加`try/except`或者`try/finally`語句(或者它們的組合)進行處理。 有些時候,條件語句可以實現和異常處理同樣的功能,但是條件語句可能在自然性和可讀性上差些。而從另一方面來看,某些程序中使用`if/else`實現會比使用`try/except`要好。讓我們看幾個例子。 假設有一個字典,我們希望打印出存儲在特定的鍵下面的值。如果該鍵不存在,那么什么也不做。代碼可能像下面這樣寫: ``` def describePerson(person): print "Description of", person["name"] print "Age:", person["age"] if "occupation" in person: print "Occupation:", person["occupation"] ``` 如果給程序提供包含名字`Throatwobbler Mangrove`和年齡`42`(沒有職業)的字典的函數,會得到如下輸出: ``` Description of Throatwobbler Mangrove Age: 42 ``` 如果添加了職業`camper`,會的到如下輸出: ``` Description of Throatwobbler Mangrove Age: 42 Occupation: camper ``` 代碼非常直觀,但是效率不高(盡管這里主要關心的是代碼的簡潔性)。程序會兩次查找`"occupation"`鍵,其中一次用來檢查鍵是否存在(在條件語句中),另外一次獲得值(打印)。另外一個解決方案如下: ``` def describePerson(person): print "Description of", person["name"] print "Age:", person["age"] try: print "Occupation: " + person["occupation"] except KeyError: pass ``` _注:這里在打印職業時使用加號而不是逗號。否則字符串`"Occupation:"`在異常引發之前就會被輸出。_ 這個程序直接假定`"occupation"`鍵存在。如果它的確存在,那么就會省事一些。直接取出它的值再打印輸出即可——不用額外檢查它是否真的存在。如果該鍵不存在,則會引發`KeyError`異常,而被`except`子句捕捉到。 在查看對象是否存在特定特性時,`try/except`也很有用。假設想要查看某對象是否有`write`特性,那么可以使用如下代碼: ``` try: obj.write except AttributeError: print "The object is not writeable" else: print "The object is writeable" ``` 這里的`try`子句僅僅訪問特性而不用對它做別的有用的事情。如果`AttributeError`異常引發,就證明對象沒有這個特性,反之存在該特性。這是實現第七章中介紹的`getattr`(7.2.8節)方法的替代方法,至于更喜歡哪種方法,完全是個人喜好。其實在內部實現`getattr`時也是使用這種方法:它試著訪問特性并且捕捉可能引發的`AttributeError`異常。 注意,這里所獲得的效率提高并不多(微乎其微),一般來說(除非程序有性能問題)程序開發人員不用過多擔心這類優化問題。在很多情況下,使用`try/except`語句比使用`if/else`會更自然一些(更“Python化”),應該養成盡可能使用`try/except`語句的習慣。 ## 8.12 小結 本章的主題如下。 ? 異常對象:異常情況(比如發生錯誤)可以用異常對象表示。它們可以用幾種方法處理,但是如果忽略的話,程序就會中止。 ? 警告:警告類似于異常,但是(一般來說)僅僅打印錯誤信息。 ? 引發異常:可以使用`raise`語句引發異常。它接受異常類或者異常實例作為參數。還能提供兩個參數(異常和錯誤信息)。如果在except子句中不使用參數調用`raise`,它就會“重新引發”該子句捕捉到的異常。 ? 自定義異常類:用繼承`Exception`類的方法可以創建自己的異常類。 ? 捕捉異常:使用`try`語句的`except`子句捕捉異常。如果在`except`子句中不特別指定異常類,那么所有的異常都會被捕捉。異常可以放在元組中以實現多個異常的指定。如果給`except`提供兩個參數,第二個參數就會綁定到異常對象上。同樣,在一個`try/except`語句中能包含多個`except`子句,用來分別處理不同的異常。 ? `else`子句:除了`except`子句,可以使用`else`子句。如果主`try`塊中沒有引發異常,`else`子句就會被執行。 ? `finally`:如果需要確保某些代碼不管是否有異常引發都要執行(比如清理代碼),那么這些代碼可以放置在`finally`(注意,在Python2.5以前,在一個`try`語句中不能同時使用`except`和`finally`子句——但是一個子句可以放置在另一個子句中)子句中。 ? 異常和函數:在函數內引發異常時,它就會被傳播到函數調用的地方(對于方法也是一樣)。 ### 8.12.1 本章的新函數 本章涉及的新函數如表8-2所示。 表8-2 本章的新函數 ``` warnings,filterwarnings(action, ...) 用于過濾警告 ``` ### 8.12.2 接下來學什么 本章講異常,內容可能有些意外(雙關語),而下一章的內容真的很不可思議,恩,近乎不可思議。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看