一文搞懂 Python 的 abc.ABC基類
在 Python 里,abc.ABC 是“抽象基類(Abstract Base Class)”的基類。你可以把它理解為:
一個(gè)“專門用來被繼承的類”,它本身不一定能直接用來實(shí)例化,主要作用是 規(guī)定接口、做類型約束。
在日常寫 Python 類時(shí),你可能見過這樣的代碼:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
這里的 ABC 是什么?為什么要這么寫?不寫會(huì)怎樣?這篇文章就來系統(tǒng)講清楚:abc.ABC 到底是干嘛用的、怎么用、什么時(shí)候值得用。
一、abc.ABC是什么?
1. 它來自哪個(gè)模塊?
ABC 定義在標(biāo)準(zhǔn)庫 abc 模塊中:
from abc import ABC
abc 全稱 Abstract Base Classes,即“抽象基類”。ABC 就是一個(gè)“抽象基類的基類”。
2. 抽象基類(Abstract Base Class)是什么?
抽象基類(簡(jiǎn)稱 ABC)是用來 定義接口規(guī)范 的類,它通常具有這些特點(diǎn):
- 不打算直接被實(shí)例化(至少設(shè)計(jì)上是這樣);
- 定義了一些必須由子類實(shí)現(xiàn)的方法;
- 在 Python 中,這些“必須實(shí)現(xiàn)的方法”是通過
@abstractmethod(以及其他類似的裝飾器)標(biāo)記的。
通俗理解:
抽象基類是“接口模板”,子類是“真正干活的實(shí)現(xiàn)”。
二、為什么要用abc.ABC?不用行不行?
先說結(jié)論:不用也“能跑”,但容易亂。
1. 不用abc.ABC會(huì)出現(xiàn)的問題
想象你寫了一個(gè)“動(dòng)物”類:
class Animal:
def speak(self):
raise NotImplementedError
約定所有子類都要重寫 speak。
問題在于:
- 你 可以實(shí)例化
Animal,直到你調(diào)用speak()才會(huì)在運(yùn)行時(shí)爆出NotImplementedError; - 沒有任何機(jī)制在 類定義階段 檢查“子類到底有沒有實(shí)現(xiàn)
speak”。
這意味著:
- 錯(cuò)誤暴露得晚(運(yùn)行時(shí)才知道);
- 新人接手代碼時(shí),不知道哪些方法是強(qiáng)制要實(shí)現(xiàn)的,只能靠文檔或注釋。
2. 使用abc.ABC帶來的好處
有了 abc.ABC 和 @abstractmethod:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
好處立刻顯現(xiàn):
不能直接實(shí)例化抽象類
a = Animal() # TypeError: Can't instantiate abstract class Animal with abstract method speak
子類必須實(shí)現(xiàn)所有抽象方法才能被實(shí)例化
class Dog(Animal): pass Dog() # TypeError: Can't instantiate abstract class Dog with abstract method speak一旦子類漏實(shí)現(xiàn)了
speak,在你實(shí)例化它的時(shí)候就會(huì)立刻報(bào)錯(cuò)。抽象方法在 IDE / 文檔里會(huì)有明確標(biāo)記,接口規(guī)范更加清晰。
總結(jié)一下:
不用可以,但用了更安全、更清晰、更工程化。
三、abc.ABC的基本用法
1. 定義抽象基類
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
"""讓動(dòng)物發(fā)出叫聲"""
pass
def sleep(self):
"""普通實(shí)例方法,可以有默認(rèn)實(shí)現(xiàn)"""
print("Zzz...")
這里重點(diǎn):
Animal繼承了ABC,因此它是一個(gè)抽象基類;speak上加了@abstractmethod,代表是抽象方法;sleep是普通方法,可以給出一個(gè)默認(rèn)實(shí)現(xiàn)。
2. 定義子類并實(shí)現(xiàn)抽象方法
class Dog(Animal):
def speak(self):
print("Woof!")
class Cat(Animal):
def speak(self):
print("Meow~")
現(xiàn)在:
Dog().speak() # Woof! Cat().sleep() # Zzz...
而如果寫:
class Fish(Animal):
# 沒有實(shí)現(xiàn) speak
pass
Fish() # TypeError: Can't instantiate abstract class Fish with abstract method speak
四、抽象方法可以有方法體嗎?
很多人以為“抽象方法就不能寫代碼”,其實(shí)這是誤解。
在 Python 中,抽象方法完全可以有實(shí)現(xiàn):
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def process(self, data):
print("Base processing:", data)
子類必須重寫 process,但可以通過 super() 調(diào)用父類的實(shí)現(xiàn):
class Child(Base):
def process(self, data):
super().process(data)
print("Child extra processing:", data)
c = Child()
c.process("hello")
# 輸出:
# Base processing: hello
# Child extra processing: hello
所以:
@abstractmethod 的意義不是“這里不能寫代碼”,而是“子類必須重寫這個(gè)方法”。
五、抽象屬性、類方法、靜態(tài)方法
abc 還提供了更多裝飾器,可以和 @abstractmethod 組合使用。
1. 抽象屬性
from abc import ABC, abstractmethod
class Shape(ABC):
@property
@abstractmethod
def area(self):
"""返回面積"""
pass
子類必須實(shí)現(xiàn)屬性 area:
class Circle(Shape):
def __init__(self, r):
self.r = r
@property
def area(self):
return 3.14 * self.r * self.r
如果子類不實(shí)現(xiàn)該屬性,實(shí)例化時(shí)會(huì)報(bào) TypeError。
2. 抽象類方法 / 靜態(tài)方法
from abc import ABC, abstractmethod
class Serializer(ABC):
@classmethod
@abstractmethod
def from_string(cls, s: str):
"""從字符串反序列化"""
pass
@staticmethod
@abstractmethod
def validate(data):
"""驗(yàn)證數(shù)據(jù)是否合法"""
pass
子類必須實(shí)現(xiàn) from_string 和 validate:
class JsonSerializer(Serializer):
@classmethod
def from_string(cls, s: str):
import json
return json.loads(s)
@staticmethod
def validate(data):
return isinstance(data, (dict, list))
六、ABC與ABCMeta的關(guān)系
在內(nèi)部,ABC 是用一個(gè)特殊的 metaclass —— ABCMeta 實(shí)現(xiàn)的。
等價(jià)寫法其實(shí)是:
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
...
但日常開發(fā)中,不推薦直接用 ABCMeta,而是:
from abc import ABC
class MyABC(ABC):
...
因?yàn)椋?/p>
- 繼承
ABC可讀性更好; - 很多工具 / IDE 都默認(rèn)識(shí)別這種模式。
簡(jiǎn)單記?。?/p>
想定義抽象基類,只需要繼承 ABC,無需直接操作 ABCMeta。
七、abc.ABC在實(shí)際工程中的使用場(chǎng)景
場(chǎng)景 1:定義統(tǒng)一接口,屏蔽實(shí)現(xiàn)細(xì)節(jié)
例如設(shè)計(jì)一個(gè)消息發(fā)送系統(tǒng),希望支持多種渠道(郵箱、短信、釘釘?shù)龋?/p>
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, to, message):
"""發(fā)送消息"""
pass
各種具體實(shí)現(xiàn):
class EmailNotifier(Notifier):
def send(self, to, message):
print(f"Email to {to}: {message}")
class SMSNotifier(Notifier):
def send(self, to, message):
print(f"SMS to {to}: {message}")
業(yè)務(wù)代碼只依賴 Notifier 接口:
def notify_user(notifier: Notifier, user, text):
notifier.send(user.contact, text)
其中 notifier 可以是任意符合接口的實(shí)現(xiàn)類。
日后需要新增一個(gè) DingTalkNotifier,只要繼承 Notifier 并實(shí)現(xiàn) send 即可。
場(chǎng)景 2:插件系統(tǒng)或擴(kuò)展點(diǎn)
例如你寫了一個(gè)框架,想讓別人“插入自己的算法”,就可以用 abc.ABC 規(guī)定一個(gè)基類,讓所有插件作者都按這個(gè)接口實(shí)現(xiàn):
class Plugin(ABC):
@abstractmethod
def name(self) -> str:
pass
@abstractmethod
def run(self, data):
pass
框架側(cè)邏輯:
def run_all_plugins(plugins, data):
for p in plugins:
print(f"Running plugin: {p.name()}")
p.run(data)
插件作者只需要按照約定繼承并實(shí)現(xiàn)方法。
如果漏實(shí)現(xiàn),就會(huì)在實(shí)例化 / 注冊(cè)時(shí)直接報(bào)錯(cuò),避免線上才發(fā)現(xiàn)錯(cuò)誤。
場(chǎng)景 3:為類型檢查 / IDE 提供更好支持
抽象基類為 靜態(tài)類型檢查 提供了很好的基礎(chǔ):
def do_something(animal: Animal):
animal.speak()
類型檢查工具(如 mypy、pyright)能夠根據(jù) Animal 的定義更準(zhǔn)確地判斷類型是否匹配;IDE 也能更好地做自動(dòng)補(bǔ)全。
八、和“鴨子類型”的關(guān)系
Python 一直強(qiáng)調(diào)“鴨子類型”:
像鴨子一樣走路、像鴨子一樣叫,那它就是鴨子。
按照鴨子類型哲學(xué),你甚至不用繼承任何基類,方法名對(duì)得上就可以被當(dāng)成“鴨子”。
那抽象基類是不是和鴨子類型相矛盾?其實(shí):
- 抽象基類強(qiáng)調(diào) 顯式的接口約束;
- 鴨子類型強(qiáng)調(diào) 行為匹配即可。
在大型項(xiàng)目、多人協(xié)作中,顯式約束通常更安全、更易維護(hù);
在小腳本、試驗(yàn)性代碼中,隨意一點(diǎn)無可厚非。
所以:
abc.ABC 不是為了推翻鴨子類型,而是在需要“約束和清晰接口”的地方,給你一把更強(qiáng)的工具。
九、使用abc.ABC的最佳實(shí)踐
- 當(dāng)你寫的類“本來就是用來被繼承”的時(shí)候,用
ABC明確表達(dá)意圖; - 所有需要子類強(qiáng)制實(shí)現(xiàn)的方法,都應(yīng)該加上
@abstractmethod; - 抽象方法可以帶默認(rèn)實(shí)現(xiàn),但請(qǐng)?jiān)?docstring 中說明子類是否需要調(diào)用
super(); - 不要濫用:
- 小型腳本、一次性代碼,沒必要刻意引入
ABC; - 對(duì)外暴露的庫、框架、長期維護(hù)的項(xiàng)目,強(qiáng)烈建議使用。
- 小型腳本、一次性代碼,沒必要刻意引入
十、總結(jié):一句話記住abc.ABC
abc.ABC是 Python 用來定義 抽象基類 的基類;- 你通過繼承
ABC+@abstractmethod來定義“必須由子類實(shí)現(xiàn)”的接口; - 它的作用是:讓接口約定更明確,讓錯(cuò)誤更早暴露,讓多人協(xié)作更安全。
如果你在寫庫、寫框架、寫可復(fù)用組件,abc.ABC 是值得熟練掌握的工具。
到此這篇關(guān)于一文搞懂 Python 的 abc.ABC基類的文章就介紹到這了,更多相關(guān)Python abc.ABC 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python簡(jiǎn)單計(jì)算文件MD5值的方法示例
這篇文章主要介紹了Python簡(jiǎn)單計(jì)算文件MD5值的方法,涉及Python文件讀取、hash運(yùn)算及md5加密等相關(guān)操作技巧,需要的朋友可以參考下2018-04-04
Python numpy 提取矩陣的某一行或某一列的實(shí)例
下面小編就為大家分享一篇Python numpy 提取矩陣的某一行或某一列的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-04-04
Python利用腳本實(shí)現(xiàn)自動(dòng)發(fā)送電子郵件
這篇文章主要為大家詳細(xì)介紹了Python如何利用腳本實(shí)現(xiàn)自動(dòng)發(fā)送電子郵件功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-01-01
python re.sub()替換正則的匹配內(nèi)容方法
今天小編就為大家分享一篇python re.sub()替換正則的匹配內(nèi)容方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-07-07

