前言:之前工作時用python完成一個利用串口發SCPI與單片機交互通信的命令列窗口,在實現功能的時候發現用python對數據結果無論是最終正確值的回傳或錯誤值的回傳都可以直接return給主介面。顯然直接return不同意義的資料是不行的,所以採用異常機制來處理錯誤值的資料。因為之前對異常這方面了解的比較少,在此查了點資料並整理個小筆記。
文章目錄
總結
(免費學習推薦:python影片教學#)
一、對異常的理解
1、什麼是異常
異常即“與正常情況不同”,何為正常?正常便是解釋器在解釋程式碼時,我們所寫的程式碼符合解釋器定義的規則,即為正常,當解釋器發現某段程式碼符合語法但有可能出現不正常的情況時,解釋器便會發出一個事件,中斷程式的正常執行。這個中斷的訊號便是一個異常訊號。所以,整體解釋就是,在解釋器發現到程式出現錯誤的時候,則會產生一個異常,若程式沒有處理,則會將該異常拋出,程式的運作也隨之終止。我們可以在一個空白的.py檔中寫一句int(「m」),運行後結果如下。
這一串字體為解釋器拋出的一系列錯誤訊息,因為int()傳入的參數只支援數字字串和數字,顯然'm'不屬於數字字串傳入參數錯誤所以解釋器報“valueError”的錯誤。
2、錯誤和異常的區別
對於python錯誤的概述:它指的是程式碼運行前的語法或邏輯錯誤。拿常規語法錯誤來說,當我們寫的程式碼過不了語法偵測時,則會直接出現語法錯誤,必須在程式執行前就改正,不然寫的程式碼將毫無意義,程式碼是不運作的,也無法捕獲得到。舉個例子,在.py檔輸入if a = 1 print(“hello”),輸出結果如下:
Traceback (most recent call last): File "E:/Test_code/test.py",line 1 if a = 1 print("hello") ^SyntaxError: invalid syntax
函數print() 被檢查到有錯誤,是它前面缺少了一個冒號: ,所以解析器會復現句法錯誤的那行程式碼,並用一個小「箭頭」指向行裡偵測到的第一個錯誤,所以我們可以直接找到對應的位置修改其語法。當然除了語法錯誤,還有很多程式奔潰的錯誤,如記憶體溢位等,這類錯誤往往比較隱密。
相較於錯誤,python異常主要在程式執行過程中,程式遇見邏輯或演算法問題,這時解釋器如果可以處理,則沒問題,如果處理不了,便直接終止程序,便將異常拋出,如第1小點的int('m')例子,因為參數傳入錯誤導致程式出錯。這種因為邏輯產生的異常五花八門,還好我們的解釋器都內建好了各種異常的種類,讓我們知道是什麼樣的異常出現,好讓我們「對症下藥」。
這裡注意一點,上述語法錯誤是可辨識的錯誤,所以解釋器也會預設拋出一個SyntaxError異常訊息回饋給程式設計師。所以本質上大部分錯誤都是可輸出列印的,只是因為錯誤程式碼不運行,也就沒辦法處理,所以捕捉錯誤的異常訊息就變得沒意義。
3、常見python異常種類
這裡貼上我們在寫程式碼時最常見的異常類型,如果遇到其他種類的異常,當然是選擇白度啦~
異常名稱 | 名稱解析 |
---|---|
#BaseException | 所有例外的基底類別 |
SystemExit | 解釋器請求退出 |
KeyboardInterrupt | 使用者中斷執行(通常是輸入^C) |
Exception | 常規錯誤的基類 |
迭代器沒有更多的值 | |
生成器(generator)發生異常來通知退出 | |
所有的內建標準異常的基底類別 | |
所有數值計算錯誤的基底類別 | ##FloatingPointError |
OverflowError | |
ZeropisionError | |
AssertionError | |
AttributeError | |
EOFError | |
EnvironmentError | |
IOError | |
##OSError | |
WindowsError | |
##ImportError | 匯入模組/物件失敗 |
LookupError | 在無效資料查詢的基底類別 |
IndexError | #序列中沒有此索引(index) |
KeyError | 映射中沒有這個鍵 |
MemoryError | #內存溢出錯誤(對於Python 解釋器不是致命的) |
NameError | 未宣告/初始化物件(沒有屬性) |
UnboundLocalError | 存取未初始化的本地變數 |
#ReferenceError | 弱引用(Weak reference)試圖存取已經垃圾回收了的物件 |
RuntimeError | 一般的執行階段錯誤 |
NotImplementedError | 尚未實作的方法 |
SyntaxError Python | 語法錯誤 |
#IndentationError | 縮排錯誤 |
TabError Tab | 與空白混合 |
SystemError | 一般的解釋器系統錯誤 |
TypeError | 對類型無效的操作 |
ValueError | #傳入無效的參數 |
UnicodeError Unicode | 相關的錯誤 |
UnicodeDecodeError Unicode | 解碼時的錯誤 |
##UnicodeEncodeError Unicode | 編碼時錯誤 |
UnicodeTranslateError Unicode | |
#Warning | 警告的基類 |
DeprecationWarning | 關於被棄用的特徵的警告 |
FutureWarning | 關於建構將來語意會有改變的警告 |
OverflowWarning | 舊的關於自動提升為長整數(long)的警告 |
PendingDeprecationWarning | 關於特性將會被廢棄的警告 |
#RuntimeWarning | 可疑的運行時行為(runtime behavior)的警告 |
#SyntaxWarning | 可疑的語法的警告 |
UserWarning | 使用者程式碼產生的警告 |
# 二、python五大异常处理机制 我们明白了什么是异常后,那么发现异常后怎么处理,便是我们接下来要解决的问题。这里将处理异常的方式总结为五种。 1、默认异常处理机制 “默认”则说明是解释器默认做出的行为,如果解释器发现异常,并且我们没有对异常进行任何预防,那么程序在执行过程中就会中断程序,调用python默认的异常处理器,并在终端输出异常信息。刚才举过的例子:int(“m”),便是解释器因为发现参数传入异常,这种异常解释器“无能为力”,所以它最后中断了程序,并将错误信息打印输出,告诉码农朋友们:你的程序有bug!!! 2、try…except…处理机制 我们把可能发生错误的语句放在try语句里,用except来处理异常。每一个try,都必须至少有一个或者多个except。举一个最简单的例子如下,在try访问number的第500个元素,很明显数组越界访问不了,这时候解释器会发出异常信号:IndexError,接着寻找后面是否有对应的异常捕获语句except ,如果有则执行对应的except语句,待except语句执行完毕后,程序将继续往下执行。如果没有对应的except语句,即用户没有处理对应的异常,这时解释器会直接中断程序并将错误信息打印输出。 number = 'hello'try: print(number[500]) #数组越界访问except IndexError: print("下标越界啦!")except NameError: print("未声明对象!")print("继续运行...") 输出结果如下,因为解释器发出异常信号是IndexError,所以执行下标越界语句。 下标越界啦! 继续运行... 为了解锁更多用法,我们再将例子改一下,我们依然在try访问number的第500个元素,造成访问越界错误,这里的except用了as关键字可以获得异常对象,这样子便可获得错误的属性值来输出信息。 number = 'hello'try: print(number[500]) #数组越界访问except IndexError as e: print(e)except Exception as e: #万能异常 print(e)except: #默认处理所有异常 print("所有异常都可处理")print("继续运行...") 输出结果如下所示,会输出系统自带的提示错误:string index out of range,相对于解释器因为异常自己抛出来的一堆红色刺眼的字体,这种看起来舒服多了(能够“运筹帷幄”的异常才是好异常嘛哈哈哈)。另外这里用到“万能异常”Exception,基本所有没处理的异常都可以在此执行。最后一个except表示,如果没有指定异常,则默认处理所有的异常。 string index out of range继续运行... 3、try…except…finally…处理机制 finally语句块表示,无论异常发生与否,finally中的语句都要执行完毕。也就是可以很霸气的说,无论产生的异常是被except捕获到处理了,还是没被捕获到解释器将错误输出来了,都统统要执行这个finally。还是原来简单的例子加上finally语句块如下,代码如下: number = 'hello'try: print(number[500]) #数组越界访问,抛出IndexError异常except IndexError: print("下标越界啦!")finally: print("finally!")print("继续运行...") #运行 结果如下,数据越界访问异常被捕获到后,先执行except 语句块,完毕后接着执行了finally语句块。因为异常被执行,所以后面代码继续运行。 下标越界啦!finally! 继续运行... 对try语句块进行修改,打印abc变量值,因为abc变量没定义,所以会出现不会被捕获的NameError异常信号,代码如下所示: number = 'hello'try: print(abc) #变量未被定义,抛出NameError异常except IndexError: print("下标越界啦!")finally: print("finally!")print("继续运行...") #不运行 结果如下,因为NameError异常信号没法被处理,所以解释器将程序中断,并将错误信息输出,但这过程中依然会执行finally语句块的内容。因为程序被迫中断了,所以后面代码不运行。 finally! #异常没被捕获,也执行了finallyTraceback (most recent call last): File "E:/Test_code/test.py",line 3,in <module> print("abc")NameError: name 'abc' is not defined 理解到这里,相信:try…finally…这种机制应该也不难理解了,因为省略了except 捕获异常机制,所以异常不可能被处理,解释器会将程序中断,并将错误信息输出,但finally语句块的内容依然会被执行。例子代码如下: number = 'hello'try: print(abc) #变量未被定义,抛出NameError异常finally: print("finally!")print("继续运行...") 运行结果: finally! #异常没被捕获,也执行了finallyTraceback (most recent call last): File "E:/Test_code/test.py",line 3,in <module> print("abc")NameError: name 'abc' is not defined 4、assert断言处理机制 assert语句先判断assert后面紧跟的语句是True还是False,如果是True则继续往下执行语句,如果是False则中断程序,将错误信息输出。 assert 1 == 1 #为True正常运行assert 1 == 2 #为False,终止程序,错误信息输出 5、with…as处理机制 with…as一般常用在文件处理上,我们平时在使用类似文件的流对象时,使用完毕后要调用close方法关闭,很麻烦,这里with…as语句提供了一个非常方便且人性的替代方法,即使突发情况也能正常关闭文件。举个例子代码如下,open打开文件后将返回的文件流对象赋值给fd,然后在with语句块中使用。 with open('e:/test.txt','r') as fd: fd.read() print(abc) #变量未被定义,程序终止,错误信息输出print("继续运行...") 正常情况下,这里的with语句块完毕之后,会自动关闭文件。但如果with语句执行中发生异常,如代码中的变量未定义异常,则会采用默认异常处理机制,程序终止,错误信息输出,后面代码不被运行,文件也会正常关闭。 三、python异常自定义 说了这么多异常的使用,终于可以回到我前言所说的在实际项目中存在的问题,即错误码的返回和数值的返回是冲突的(因为错误码也是数值),这时候便可以用异常的抛出和捕获来完成错误码的传递,即try和except 。但系统发生异常时抛出的是系统本身定义好的异常类型,跟自己的错误码又有何关系?这就是我接下来要说的内容:如何定义自己的异常并且能够被except 所捕获。 1、异常自定义 实际开发中,有时候系统提供的异常类型往往都不能满足开发的需求。这时候就要使用到异常的自定义啦,你可以通过创建一个新的异常类来拥有自己的异常。自己定义的异常类继承自 Exception 类,可以直接继承,或者间接继承。栗子举起来: class MyException(Exception): '''自定义的异常类''' def __init__(self, error_num): #异常类对象的初始化属性 self.error_num = error_num def __str__(self): #返回异常类对象说明信息 err_info = ['超时错误','接收错误'] return err_info[self.error_num] 该类继承自Exception 类,并且新类的名字为MyException,这跟前面我们一直在用的IndexError这个异常类一样,都是继承自Exception 类。 2、异常抛出raise 现在我们自己定义的错误定义好了(上面的MyException),怎么能像IndexError一样让except捕获到呢?于是乎raise关键字派上用场。我们在异常机制中用try…except时,一般都是将可能产生的错误代码放到try语句块中,这时出现异常则系统便会自动将其抛出,比如IndexError,这样except就能捕获到,所以我们只要将自定义的异常在需要的时候将其抛出即可。 try: raise MyException(0) # 自己定义的错误类,将错误码为0的错误抛出except MyException as e: print(e) # 输出的是__str__返回的内容,即“超时错误” 这里我直接将自己定义的错误抛出,…as e就是把得到的错误当成对象e,这样才可以访问其属性和方法。因为自己定义的错误中可以支持多个错误码(本质还是MyException这个错误),所以便可实现传入不同错误码就可打印不同错误信息。 3、异常捕获 只要我们在try中将错误raise出来,except就可以捕获到(当然,异常必须是Exception 子类才能被捕获),将前面两个例子整合起来,代码如下: '''错误码:0代表超时错误,1代表接收错误'''class MyException(Exception): '''自定义的异常类''' def __init__(self, error_num): # 异常类对象的初始化属性 self.error_num= error_num def __str__(self): # 返回异常类对象指定错误码的信息 err_info = ['超时错误','接收错误'] return err_info[self.error_num]def fun() raise MyException(1) # 抛出异常对象,传入错误码1def demo_main(): try: fun() except MyException as ex: # 这里要使用MyException进行捕获,对象为ex print(ex) # 输出的是__str__部分返回的内容,即“接收错误” print(ex.error_num) # 输出的是__init__中定义的error_num,即1demo_main() #此处开始运行 代码从demo_main函数开始执行,进入try语句块,语句块中的fun()函数模拟代码运行失败时raise 自定义的异常,except 正常接收后通过as 关键字得到异常对象,访问该异常对象,便可正常输出自定义的异常信息和自定义的错误码。 四、异常使用注意事项 此注意事项参考博文:异常机制使用细则. 1、不要太依赖异常机制 python 的异常机制非常方便,对于信息的传递中十分好用(这里信息的传递主要有三种,参数传递,全局变量传递,以及异常机制传递),但滥用异常机制也会带来一些负面影响。过度使用异常主要表现在两个方面:①把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单地引发异常来代苦所有的错误处理。②使用异常处理来代替流程控制。例子如下: buf = "hello"#例1:使用异常处理来遍历arr数组的每个元素try: i = 0 while True: print (buf [i]) i += 1except: pass#例2:使用流程控制避免下标访问异常i = 0while i < len(buf ): print(buf [i]) i += 1 例1中假如循环过度便会下标访问异常,这时候把错误抛出,再进行一系列处理,显然是不可取的,因为异常机制的效率比正常的流程控制效率差,显然例2中简单的业务流程就可以避开这种错误。所以不要熟悉了异常的使用方法后,遇到这种简单逻辑,便不管三七二十一引发异常后再进行解决。对于完全己知的错误和普通的错误,应该编写处理这种错误的代码,增加程序的健壮性。只有对于外部的、不能确定和预知的运行时错误才使用异常。 2、不要在 try 块中引入太多的代码 在try 區塊裡放置大量的程式碼,這看起來很“簡單”,程式碼框架很容易理解,但因為try 區塊裡的程式碼過於龐大,業務過於複雜,就會造成try 區塊中出現異常的可能性大大增加,從而導致分析異常原因的難度也大大增加。 3、不要忽略捕獲到的例外 不要忽略異常!既然己捕獲到異常,那麼 except 區塊理應做些有用的事情,及處理並修復異常。 except 區塊整個為空,或僅僅列印簡單的異常資訊都是不妥的!具體的處理方式為: 總結 本文從系統預設的異常起手,說明了什麼是異常並總結了系統常見的異常類,接著寫了怎麼自訂異常,從異常的定義到拋出再到獲取完成自訂異常的定義和使用,最後再總結了python異常使用時的注意事項。 # |
以上是讀懂Python的異常機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!