全網最適合入門的物件導向程式設計教程:24 類和物件的 Python 實現-異常的捕獲與處理:try/except 語句、檔案讀寫示例、Exception 引用

FreakStudio發表於2024-07-25

全網最適合入門的物件導向程式設計教程:24 類和物件的 Python 實現-異常的捕獲與處理:try/except 語句、檔案讀寫示例、Exception 引用

image

摘要:

本文主要介紹了在使用 Python 物件導向程式設計時,如何使用 try/except 語句捕獲並處理異常,並輔以 CSV 檔案讀寫為例進行講解,同時說明了如何對 Exception 物件進行引用。

原文連結:

FreakStudio的部落格

往期推薦:

學嵌入式的你,還不會物件導向??!

全網最適合入門的物件導向程式設計教程:00 物件導向設計方法導論

全網最適合入門的物件導向程式設計教程:01 物件導向程式設計的基本概念

全網最適合入門的物件導向程式設計教程:02 類和物件的 Python 實現-使用 Python 建立類

全網最適合入門的物件導向程式設計教程:03 類和物件的 Python 實現-為自定義類新增屬性

全網最適合入門的物件導向程式設計教程:04 類和物件的Python實現-為自定義類新增方法

全網最適合入門的物件導向程式設計教程:05 類和物件的Python實現-PyCharm程式碼標籤

全網最適合入門的物件導向程式設計教程:06 類和物件的Python實現-自定義類的資料封裝

全網最適合入門的物件導向程式設計教程:07 類和物件的Python實現-型別註解

全網最適合入門的物件導向程式設計教程:08 類和物件的Python實現-@property裝飾器

全網最適合入門的物件導向程式設計教程:09 類和物件的Python實現-類之間的關係

全網最適合入門的物件導向程式設計教程:10 類和物件的Python實現-類的繼承和里氏替換原則

全網最適合入門的物件導向程式設計教程:11 類和物件的Python實現-子類呼叫父類方法

全網最適合入門的物件導向程式設計教程:12 類和物件的Python實現-Python使用logging模組輸出程式執行日誌

全網最適合入門的物件導向程式設計教程:13 類和物件的Python實現-視覺化閱讀程式碼神器Sourcetrail的安裝使用

全網最適合入門的物件導向程式設計教程:全網最適合入門的物件導向程式設計教程:14 類和物件的Python實現-類的靜態方法和類方法

全網最適合入門的物件導向程式設計教程:15 類和物件的 Python 實現-__slots__魔法方法

全網最適合入門的物件導向程式設計教程:16 類和物件的Python實現-多型、方法重寫與開閉原則

全網最適合入門的物件導向程式設計教程:17 類和物件的Python實現-鴨子型別與“file-like object“

全網最適合入門的物件導向程式設計教程:18 類和物件的Python實現-多重繼承與PyQtGraph串列埠資料繪製曲線圖

全網最適合入門的物件導向程式設計教程:19 類和物件的 Python 實現-使用 PyCharm 自動生成檔案註釋和函式註釋

全網最適合入門的物件導向程式設計教程:20 類和物件的Python實現-組合關係的實現與CSV檔案儲存

全網最適合入門的物件導向程式設計教程:21 類和物件的Python實現-多檔案的組織:模組module和包package

全網最適合入門的物件導向程式設計教程:22 類和物件的Python實現-異常和語法錯誤

全網最適合入門的物件導向程式設計教程:23 類和物件的Python實現-丟擲異常

更多精彩內容可看:

給你的 Python 加加速:一文速通 Python 平行計算

一文搞懂 CM3 微控制器除錯原理

肝了半個月,嵌入式技術棧大彙總出爐

電子計算機類比賽的“武林秘籍”

一個MicroPython的開源專案集錦:awesome-micropython,包含各個方面的Micropython工具庫

文件和程式碼獲取:

可訪問如下連結進行對文件下載:

https://github.com/leezisheng/Doc

image

本文件主要介紹如何使用 Python 進行物件導向程式設計,需要讀者對 Python 語法和微控制器開發具有基本瞭解。相比其他講解 Python 物件導向程式設計的部落格或書籍而言,本文件更加詳細、側重於嵌入式上位機應用,以上位機和下位機的常見串列埠資料收發、資料處理、動態圖繪製等為應用例項,同時使用 Sourcetrail 程式碼軟體對程式碼進行視覺化閱讀便於讀者理解。

相關示例程式碼獲取連結如下:https://github.com/leezisheng/Python-OOP-Demo

正文

當 Python 指令碼發生異常時我們需要捕獲處理它,否則程式會終止執行。捕捉異常可以使用 try/except 語句。try/except 語句用來檢測 try 語句塊中的錯誤,從而讓 except 語句捕獲異常資訊並處理。

如果你不想在異常發生時結束你的程式,只需在 try 裡捕獲它。以下為簡單的 try....except...else 的語法:

try:
<語句>       
except <名字>:
<語句>

try 語句的工作原理如下:

  • (1)首先,執行 try 子句(try 和 except 關鍵字之間的(多行)語句)。

  • (2)如果沒有觸發異常,則跳過 except 子句,try 語句執行完畢。

  • (3)如果在執行 try 子句時發生了異常,則跳過該子句中剩下的部分。如果異常的型別與 except 關鍵字後指定的異常相匹配,則會執行 except 子句,然後跳到 try/except 程式碼塊之後繼續執行。

  • (4)如果發生的異常與 except 子句中指定的異常不匹配,則它會被傳遞到外層的 try 語句中;如果沒有找到處理控制代碼,則它是一個未處理異常且執行將停止並輸出一條錯誤訊息。

示例程式碼如下:

class SensorClass(SerialClass):  
    ...
    _# 類的初始化_
    def __init__(self,port:str = "COM11",id:int = 0,state:int = RESPOND_MODE):
        try:
            if id <= 0 or id >= 99:
                _# 觸發異常後,後面的程式碼就不會再執行_
                raise Exception("InvalidIDError:", id)
            _# 呼叫父類的初始化方法,super() 函式將父類和子類連線_
            super().__init__(port)
            self.sensorvalue = 0
            self.sensorid    = id
            self.sensorstate = state
            print("Sensor Init")
            logging.info("Sensor Init")
        except:
            _# 當發生異常時,輸出如下語句,提醒使用者重新輸入ID號_
            print("Input error ID, Please try id : 0~99") 
         ...
if __name__ == "__main__":
    _# 建立感測器類,ID號為100_
    s = SensorClass(port  = "COM11",id = 100,state = SensorClass.RESPOND_MODE)

如下為執行結果:

image

以上方式 try-except 語句捕獲所有發生的異常。但這不是一個很好的方式,我們不能透過該程式識別出具體的異常資訊。因為它捕獲所有的異常。

你也可以使用相同的 except 語句來處理多個異常資訊,如下所示:

try:
    正常的操作
   ......................
except(Exception1[, Exception2[,...ExceptionN]]):
   發生以上多個異常中的一個,執行這塊程式碼
   ......................
else:
    如果沒有異常執行這塊程式碼

try 語句可以有多個 except 子句來為不同的異常指定處理程式。 但最多隻有一個處理程式會被執行。處理程式只處理對應的 try 子句中發生的異常,而不處理同一 try 語句內其他處理程式中的異常。except 子句可以用帶圓括號的元組來指定多個異常,例如:

... except (RuntimeError, TypeError, NameError):
...     pass

示例程式碼如下所示,我們在 SensorClass 感測器類的初始化方法中,加入對輸入 port 埠號資料型別的檢查,如果不是 str 型別,則丟擲 TypeError 異常:

def __init__(self,port:str = "COM11",id:int = 0,state:int = RESPOND_MODE):
        try:
            _# 判斷輸入埠號是否為str型別_
            if type(port) is not str:
                raise TypeError("InvalidPortError:",port)
            _# 判斷ID號是否在0~99之間_
            if id <= 0 or id >= 99:
                _# 觸發異常後,後面的程式碼就不會再執行_
                _# 當傳遞給函式或方法的引數型別不正確或者引數的值不合法時,會引發此異常。_
                raise ValueError("InvalidIDError:",id)

            _# 呼叫父類的初始化方法,super() 函式將父類和子類連線_
            super().__init__(port)
            self.sensorvalue = 0
            self.sensorid    = id
            self.sensorstate = state
            print("Sensor Init")
            logging.info("Sensor Init")
        except TypeError:
            _# 當發生異常時,輸出如下語句,提醒使用者重新輸入埠號_
            print("Input error com, Please try new com number")
        except ValueError:
            _# 當發生異常時,輸出如下語句,提醒使用者重新輸入ID號_
            print("Input error ID, Please try id : 0~99")

如下為執行結果,可以看到僅捕獲和處理了輸入錯誤 port 型別的異常,而沒有捕獲和處理輸入錯誤範圍的 ID 號的異常:

image

除了使用 except 塊來處理異常,我們還可以使用 finally 塊來執行一些必要的清理操作。無論是否出現異常,finally 塊中的程式碼都會被執行。如果我們需要在程式碼執行完成之後執行特定的任務(即便是遇到了異常),這將非常有用。一些常見的例子包括:清除開啟的資料庫連線;關閉開啟的檔案;向網路傳送一次關閉握手。finally 語句對於我們在 try 中執行 return 語句也非常重要。finally 中的程式碼仍然會在返回值之前執行。

例如在 FileIOClass 類中,需要讀寫 csc 檔案,基本流程就是開啟了一個 csc 檔案,然後讀取,寫入,最後關閉檔案物件。這是一套常規流程,如果我想捕捉程式碼過程中的異常,又要保證無論是否有異常,最後都必須要關閉檔案。這時候就用到 finally,示例程式碼如下:

class FileIOClass:
    def __init__(self,path:str="G:\\Python物件導向程式設計\\Demo\\file.csv"):
        '''
        初始化csv檔案和列標題
        :param path: 檔案路徑和檔名
        '''
        self.path   = path
        try:
            _# path為輸出路徑和檔名,newline=''是為了不出現空行_
            self.csvFile = open(path, "w+", newline='')
            _# rowname為列名,index-索引,data-資料_
            self.rowname = ['index', 'data']
            _# 返回一個writer物件,將使用者的資料在給定的檔案型物件上轉換為帶分隔符的字串_
            self.writer = csv.writer(self.csvFile)
            _# 寫入csv檔案的列標題_
            self.writer.writerow(self.rowname)
        except (FileNotFoundError, IOError):
            print("Could not open file")
            logging.info("Could not open file")
        except KeyboardInterrupt:
            print("Cancell the file operation")
            logging.info("Cancell the file operation")
        finally:
                self.CloseFile()

我們將檔案路徑改到一個根本不存在的路徑下,初始化 FileIOClass 例項物件,

程式碼如下:

f = FileIOClass(path = "H:\\Python物件導向程式設計\\Demo\\file.csv")

可以看到執行結果中,在發生 IOError 時,執行了 except 中的語句:

image

在第 16 行中:

16        except (FileNotFoundError, IOError):

表示只要發生 FileNotFoundError 或 IOError 中任意一個就會執行這塊程式碼。

實際上,我們可以用同樣的程式碼一次處理兩個或更多不同的異常。具體格式如下:

except(Exception1[, Exception2[,...ExceptionN]]):
   _# 發生以上多個異常中的一個,執行這塊程式碼_

當多種異常同時發生時,我們可以使用如下方式列印出相應異常的類名:

except (FileNotFoundError, IOError) as e:
            print("Could not open file",e.__class__.__name__)
            logging.info("Could not open file")

我們再次執行程式碼:

image

有時,當我們捕獲一個異常時,需要用到對 Exception 物件的引用。這通常發生在我們自己定義的有特定引數的異常,此時我們可以使用 as 語句帶上引數,作為輸出的異常資訊引數。示例程式碼如下:

except (FileNotFoundError, IOError) as e:
            print("Could not open file",e.__class__.__name__)
            print("The exception arguments were", e.args)
            logging.info("Could not open file")

執行結果如下,可以看到,在示例中輸出引數為錯誤程式碼(為 2),表示沒有這個檔案或者目錄。

image

實際上變數接收的異常值通常包含在異常的語句中。在元組的表單中變數可以接收一個或者多個值。元組通常包含錯誤字串,錯誤數字,錯誤位置。

image

相關文章