全網最適合入門的物件導向程式設計教程:50 Python函式方法與介面-介面和抽象基類

FreakStudio發表於2024-09-20

全網最適合入門的物件導向程式設計教程:50 Python 函式方法與介面-介面和抽象基類

image

摘要:

在 Python 中,介面和抽象基類(Abstract Base Classes, ABCs)都用於定義類的結構和強制子類實現特定的方法,Python 沒有內建的介面機制,但可以透過抽象基類(ABC)來模擬介面的行為。

原文連結:

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實現-丟擲異常

全網最適合入門的物件導向程式設計教程:24 類和物件的Python實現-異常的捕獲與處理

全網最適合入門的物件導向程式設計教程:25 類和物件的Python實現-Python判斷輸入資料型別

全網最適合入門的物件導向程式設計教程:26 類和物件的Python實現-上下文管理器和with語句

全網最適合入門的物件導向程式設計教程:27 類和物件的Python實現-Python中異常層級與自定義異常類的實現

全網最適合入門的物件導向程式設計教程:28 類和物件的Python實現-Python程式設計原則、哲學和規範大彙總

全網最適合入門的物件導向程式設計教程:29 類和物件的Python實現-斷言與防禦性程式設計和help函式的使用

全網最適合入門的物件導向程式設計教程:30 Python的內建資料型別-object根類

全網最適合入門的物件導向程式設計教程:31 Python的內建資料型別-物件Object和型別Type

全網最適合入門的物件導向程式設計教程:32 Python的內建資料型別-類Class和例項Instance

全網最適合入門的物件導向程式設計教程:33 Python的內建資料型別-物件Object和型別Type的關係

全網最適合入門的物件導向程式設計教程:34 Python的內建資料型別-Python常用複合資料型別:元組和命名元組

全網最適合入門的物件導向程式設計教程:35 Python的內建資料型別-文件字串和__doc__屬性

全網最適合入門的物件導向程式設計教程:36 Python的內建資料型別-字典

全網最適合入門的物件導向程式設計教程:37 Python常用複合資料型別-列表和列表推導式

全網最適合入門的物件導向程式設計教程:38 Python常用複合資料型別-使用列表實現堆疊、佇列和雙端佇列

全網最適合入門的物件導向程式設計教程:39 Python常用複合資料型別-集合

全網最適合入門的物件導向程式設計教程:40 Python常用複合資料型別-列舉和enum模組的使用

全網最適合入門的物件導向程式設計教程:41 Python常用複合資料型別-佇列(FIFO、LIFO、優先順序佇列、雙端佇列和環形佇列)

全網最適合入門的物件導向程式設計教程:42 Python常用複合資料型別-collections容器資料型別

全網最適合入門的物件導向程式設計教程:43 Python常用複合資料型別-擴充套件內建資料型別

全網最適合入門的物件導向程式設計教程:44 Python內建函式與魔法方法-重寫內建型別的魔法方法

全網最適合入門的物件導向程式設計教程:45 Python實現常見資料結構-連結串列、樹、雜湊表、圖和堆

全網最適合入門的物件導向程式設計教程:46 Python函式方法與介面-函式與事件驅動框架

全網最適合入門的物件導向程式設計教程:47 Python函式方法與介面-回撥函式Callback

全網最適合入門的物件導向程式設計教程:48 Python函式方法與介面-位置引數、預設引數、可變引數和關鍵字引數

全網最適合入門的物件導向程式設計教程:49 Python函式方法與介面-函式與方法的區別和lamda匿名函式

更多精彩內容可看:

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

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

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

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

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

Avnet ZUBoard 1CG開發板—深度學習新選擇

SenseCraft 部署模型到Grove Vision AI V2影像處理模組

文件和程式碼獲取:

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

https://github.com/leezisheng/Doc

image

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

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

正文

1. 介面

1.1 介面的概念

1994 年,GoF 在其經典之作《設計模式》中,提出了一個至關重要的程式設計原則,即“基於介面程式設計,而非面向實現程式設計”。這一原則的英文原句為“Program to an interface, not an implementation”。在此,所謂的“介面”,並非專指某一特定程式語言的介面定義,而是一種跨語言的概念。它代表了開發者向使用者提供的一組功能列表,對這些功能的理解對於程式設計實踐至關重要。簡而言之,介面定義了程式應完成的任務,而不涉及具體的實現細節。這一原則對於提升程式碼的可擴充套件性、可維護性和靈活性具有深遠影響。

透過定義介面,我們可以在實現具體功能的函式發生問題時(可能是用的沒有授權的盜版模組,被人家發現了、起訴了;也可能是庫的版本變了,某個重要的函式改名了/取消了)來減少程式碼的改動量,透過介面和實現相分離的模式,封裝不穩定的實現,暴露穩定的介面。上下游系統在使用我們開發的功能時,只需要使用介面中宣告的函式列表,這樣當實現發生變化的時候,上游系統的程式碼基本上不需要做改動,以此來降低耦合性,提高擴充套件性。

同時,透過定義介面將系統分成多個小模組,每個模組只需實現自己所需的介面,而不需要關心其他模組的實現細節。這樣在與他人協作時,我們只需要修改自己實現該介面的模組,而不需要修改其他模組。

1.2 介面的實現

常見的面嚮物件語言通常在類的介面和實現之間有明確的區分。例如,一些語言提供明確的 interface 關鍵字,用於定義類必須包含的方法,但是不需要實現。在這樣的環境下,抽象類提供一個介面,以及眾多方法中某幾個的具體實現。任何類可以明確宣告它實現自某個介面。

與 Java、Go 和 C++ 這些語言相比,Python 的介面設計方法有些不同。這些語言都提供了一個 interface 關鍵字來定義介面,而在 Python 中,卻沒有提供這個關鍵字。Python 在另一個方面與其他語言有明顯的區別,python 並不要求實現介面的類來定義介面的所有抽象方法。

2. 使用類的繼承實現介面

這裡,我們首先定義一個資料處理類的介面,宣告該類需要具有資料濾波、計算最大值、計算最小值和傅立葉變換的方法。同時定義了在初始化的過程中,需要接收哪些引數,如資料列表、濾波器點數(以平均值濾波為例,對多少個點計算平均值)等。

示例程式碼如下:

_# 使用typing模組提供的複合註解功能_
from typing import List

_# 定義資料處理介面_
class DateProcessInterface(object):
    def __init__(self,DateList:List[int],FilterLength:int):
        '''
        初始化方法
        :param DateList: 資料列表
        :param FilterLength: 對多少個點做資料濾波
        
        '''
        pass
    def DateFilter(self)->List:
        '''
        資料濾波
        :return: List
        '''
        pass
    def DateCalMax(self)->int:
        '''
        計算資料最大值
        :return: int
        '''
        pass
    def DateCalMin(self)->int:
        '''
        計算最小值
        :return: int
        '''
        pass

在定義該介面的過程中,雖然沒有實現具體操作,但是我們主要關心的是該介面類規定了資料處理的一般方法,也可以看作規範,就是我們首先會對資料進行濾波等預處理,然後計算一些時域特徵進行分析。DateProcessInterface 實際上就是標準 python 的 class,不過因為形似介面所以可以將這個類看做一個介面。

為了使用介面,首先建立一個具體類來繼承於 DateProcessInterface,也就是這個類是介面類的子類,提供了介面抽象方法的具體實現。

示例程式碼如下,這裡我們需要引入 math 庫去使用 sin 函式,引入 random 庫生成隨機數序列來模擬噪聲,引入 matplotlib 庫去繪圖,matplotlib 庫需要使用 conda 或者 pip 命令去安裝:

import math
import matplotlib.pyplot as plt
import random

_# 建立一個具體類來繼承於DateProcessInterface_
class DateProcessClass(DateProcessInterface):
    def __init__(self,DateList:List[int],FilterLength:int):
        self.DateList       = DateList
        self.FilterLength   = FilterLength
        self.TempList       = [0] * (self.FilterLength)
    def DateFilter(self)->List:
        _# 遍歷DateList_
        for index,value in enumerate(self.DateList):
            _# 把每個值都當成傳入的新的感測器的值_
            NowValue = value
            _# 表示列表求和的變數_
            sum = 0
            for i in range(self.FilterLength-1):
                _# 實現列表的移位操作_
                self.TempList[i] = self.TempList[i + 1]
                _# 實現列表求和_
                sum += self.TempList[i]
            self.TempList[self.FilterLength-1] = NowValue
            sum += self.TempList[self.FilterLength - 1]
            _# 求平均值_
            average = sum / self.FilterLength
            _# 將計算得到的平均值替換原始值_
            self.DateList[index] = average
        _# 計算完成後將TempList中元素清零_
        self.TempList = [0] * (self.FilterLength)
        return self.DateList
    def DateCalMax(self)->int:
        max_value = max(self.DateList)
        return int(max_value)
    def DateCalMin(self)->int:
        min_value = min(self.DateList)
        return int(min_value)

_# 建立l的索引列表,主要提供給plot函式作為x軸座標_
index = [x for x in range(0,100)]
_# 生成一個正弦序列_
originalsignal = [math.sin(x)*10 for x in range(0,100)]
_# 生成隨機數序列,模擬噪聲_
noise = [random.uniform(0, 5) for x in range(0,100)]
_# 將兩個列表中的元素相加_
signal = [x+y for x,y in zip(originalsignal,noise) ]
_# 建立DateProcessClass類,傳入signal.copy()_
_# 透過建立signal的複製序列,從而不改變l的原始資料_
s = DateProcessClass(signal.copy(),10)
_# 進行資料濾波_
filtersignal = s.DateFilter()
_# 曲線繪圖_
plt.plot(index,signal,'b')
plt.plot(index,filtersignal,'r')
_# 顯示影像_
plt.show()
_# 列印訊號最大值_
print("Signal Max value:",s.DateCalMax())
_# 列印訊號最小值_
print("Signal Min value:",s.DateCalMin())

我們來看一下執行效果,可以看到我們對原始資料使用 5 點的滑動平均濾波的效果(藍色是原始波形、紅色是濾波後波形):

image

image

為了防止有人直接從我們的介面類中例項化物件,我們可以使用如下程式碼,在直接呼叫介面中方法時丟擲 NotImplementedError 異常:

_# 定義資料處理介面_
class DateProcessInterface(object):
    def __init__(self,DateList:List[int],FilterLength:int):
        '''
        初始化方法
        :param DateList: 資料列表
        :param FilterLength: 對多少個點做資料濾波
        '''
        raise NotImplementedError

3. 使用抽象基類(ABC 類)實現介面

在 Python 程式設計中,抽象基類(Abstract Base Classes,簡稱 ABC)是一種非常有用的工具,用於定義介面和規範類的行為。抽象基類提供了一種機制,可以確保子類實現了特定的方法和屬性。

抽象基類,也是類,需要使用 class 關鍵字進行定義,它與普通類不同之處有兩點:

  • 抽象基類不能被例項化,只能被繼承;
  • 子類必須實現抽象基類裡定義的抽象方法,否則不能被例項化。

在 Python 中,可以使用 abc 模組來定義抽象基類。以上的 DateProcessInterface 使用抽象基類時,示例程式碼如下:

from abc import ABCMeta, abstractmethod
_# 使用抽象基類定義資料處理介面_
class DateProcessInterface(metaclass=ABCMeta):
    def __init__(self,DateList:List[int],FilterLength:int):
        '''
        初始化方法
        :param DateList: 資料列表
        :param FilterLength: 對多少個點做資料濾波
        '''
        pass

    @abstractmethod
    def DateFilter(self)->List:
        '''
        抽象方法,資料濾波
        :return: List
        '''
        pass

    @abstractmethod
    def DateCalMax(self)->int:
        '''
        抽象方法,計算資料最大值
        :return: int
        '''
        pass

    @abstractmethod
    def DateCalMin(self)->int:
        '''
        抽象方法,計算最小值
        :return: int
        '''
        pass

這裡,我們首先需要給介面類傳遞 metaclass 關鍵字引數,該關鍵字涉及到了元類程式設計的相關知識點,我們不予詳細介紹,只需知道透過分配 ABCMeta 元類,可以賦予你的類為抽象基類。接下來使用 @abstractmethod 的裝飾器實現了將方法標記為抽象,它們宣告該類的任何子類必須實現這一方法。

具體來說,使用 @abstractmethod 裝飾器的方法必須滿足以下兩個條件:

  • 方法必須是抽象方法,即只定義方法名和引數列表,但沒有實現程式碼;
  • 方法必須在抽象類或者實現抽象類介面的子類中實現。

@abstractmethod 還能註解靜態方法、類方法和屬性。 你只需保證這個註解緊靠在方法定義前即可。

class A(metaclass=ABCMeta):
    @property
    @abstractmethod
    def name(self):
        pass

    @name.setter
    @abstractmethod
    def name(self, value):
        pass

    @classmethod
    @abstractmethod
    def method1(cls):
        pass

    @staticmethod
    @abstractmethod
    def method2():
        pass

抽象基類還可以用於型別檢查,使用 isinstance()和 issubclass()函式。例如:

print(issubclass(DateProcessClass, DateProcessInterface))
print(isinstance(DateProcessClass, DateProcessInterface))

image

相關文章