在 Python 里,abc.ABC 是“抽象基类(Abstract Base Class)”的基类。你可以把它理解为:
一个“专门用来被继承的类”,它本身不一定能直接用来实例化,主要作用是 规定接口、做类型约束。
一文搞懂 Python 的 abc.ABC:从概念到实战
在日常写 Python 类时,你可能见过这样的代码:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
这里的 ABC 是什么?为什么要这么写?不写会怎样?这篇文章就来系统讲清楚:abc.ABC 到底是干嘛用的、怎么用、什么时候值得用。
一、abc.ABC 是什么?
1. 它来自哪个模块?
ABC 定义在标准库 abc 模块中:
from abc import ABC
abc 全称 Abstract Base Classes,即“抽象基类”。
ABC 就是一个“抽象基类的基类”。
2. 抽象基类(Abstract Base Class)是什么?
抽象基类(简称 ABC)是用来 定义接口规范 的类,它通常具有这些特点:
- 不打算直接被实例化(至少设计上是这样);
- 定义了一些必须由子类实现的方法;
- 在 Python 中,这些“必须实现的方法”是通过
@abstractmethod(以及其他类似的装饰器)标记的。
通俗理解:
抽象基类是“接口模板”,子类是“真正干活的实现”。
二、为什么要用 abc.ABC?不用行不行?
先说结论:不用也“能跑”,但容易乱。
1. 不用 abc.ABC 会出现的问题
想象你写了一个“动物”类:
class Animal:
def speak(self):
raise NotImplementedError
约定所有子类都要重写 speak。
问题在于:
- 你 可以实例化
Animal,直到你调用speak()才会在运行时爆出NotImplementedError; - 没有任何机制在 类定义阶段 检查“子类到底有没有实现
speak”。
这意味着:
- 错误暴露得晚(运行时才知道);
- 新人接手代码时,不知道哪些方法是强制要实现的,只能靠文档或注释。
2. 使用 abc.ABC 带来的好处
有了 abc.ABC 和 @abstractmethod:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
好处立刻显现:
-
不能直接实例化抽象类
a = Animal() # TypeError: Can't instantiate abstract class Animal with abstract method speak -
子类必须实现所有抽象方法才能被实例化
class Dog(Animal): pass Dog() # TypeError: Can't instantiate abstract class Dog with abstract method speak一旦子类漏实现了
speak,在你实例化它的时候就会立刻报错。 -
抽象方法在 IDE / 文档里会有明确标记,接口规范更加清晰。
总结一下:
不用可以,但用了更安全、更清晰、更工程化。
三、abc.ABC 的基本用法
1. 定义抽象基类
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
"""让动物发出叫声"""
pass
def sleep(self):
"""普通实例方法,可以有默认实现"""
print("Zzz...")
这里重点:
Animal继承了ABC,因此它是一个抽象基类;speak上加了@abstractmethod,代表是抽象方法;sleep是普通方法,可以给出一个默认实现。
2. 定义子类并实现抽象方法
class Dog(Animal):
def speak(self):
print("Woof!")
class Cat(Animal):
def speak(self):
print("Meow~")
现在:
Dog().speak() # Woof!
Cat().sleep() # Zzz...
而如果写:
class Fish(Animal):
# 没有实现 speak
pass
Fish() # TypeError: Can't instantiate abstract class Fish with abstract method speak
四、抽象方法可以有方法体吗?
很多人以为“抽象方法就不能写代码”,其实这是误解。
在 Python 中,抽象方法完全可以有实现:
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def process(self, data):
print("Base processing:", data)
子类必须重写 process,但可以通过 super() 调用父类的实现:
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的意义不是“这里不能写代码”,而是“子类必须重写这个方法”。
五、抽象属性、类方法、静态方法
abc 还提供了更多装饰器,可以和 @abstractmethod 组合使用。
1. 抽象属性
from abc import ABC, abstractmethod
class Shape(ABC):
@property
@abstractmethod
def area(self):
"""返回面积"""
pass
子类必须实现属性 area:
class Circle(Shape):
def __init__(self, r):
self.r = r
@property
def area(self):
return 3.14 * self.r * self.r
如果子类不实现该属性,实例化时会报 TypeError。
2. 抽象类方法 / 静态方法
from abc import ABC, abstractmethod
class Serializer(ABC):
@classmethod
@abstractmethod
def from_string(cls, s: str):
"""从字符串反序列化"""
pass
@staticmethod
@abstractmethod
def validate(data):
"""验证数据是否合法"""
pass
子类必须实现 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 的关系
在内部,ABC 是用一个特殊的 metaclass —— ABCMeta 实现的。
等价写法其实是:
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
...
但日常开发中,不推荐直接用 ABCMeta,而是:
from abc import ABC
class MyABC(ABC):
...
因为:
- 继承
ABC可读性更好; - 很多工具 / IDE 都默认识别这种模式。
简单记住:
想定义抽象基类,只需要继承
ABC,无需直接操作ABCMeta。
七、abc.ABC 在实际工程中的使用场景
场景 1:定义统一接口,屏蔽实现细节
例如设计一个消息发送系统,希望支持多种渠道(邮箱、短信、钉钉等):
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, to, message):
"""发送消息"""
pass
各种具体实现:
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}")
业务代码只依赖 Notifier 接口:
def notify_user(notifier: Notifier, user, text):
notifier.send(user.contact, text)
其中 notifier 可以是任意符合接口的实现类。
日后需要新增一个 DingTalkNotifier,只要继承 Notifier 并实现 send 即可。
场景 2:插件系统或扩展点
例如你写了一个框架,想让别人“插入自己的算法”,就可以用 abc.ABC 规定一个基类,让所有插件作者都按这个接口实现:
class Plugin(ABC):
@abstractmethod
def name(self) -> str:
pass
@abstractmethod
def run(self, data):
pass
框架侧逻辑:
def run_all_plugins(plugins, data):
for p in plugins:
print(f"Running plugin: {p.name()}")
p.run(data)
插件作者只需要按照约定继承并实现方法。
如果漏实现,就会在实例化 / 注册时直接报错,避免线上才发现错误。
场景 3:为类型检查 / IDE 提供更好支持
抽象基类为 静态类型检查 提供了很好的基础:
def do_something(animal: Animal):
animal.speak()
类型检查工具(如 mypy、pyright)能够根据 Animal 的定义更准确地判断类型是否匹配;IDE 也能更好地做自动补全。
八、和“鸭子类型”的关系
Python 一直强调“鸭子类型”:
像鸭子一样走路、像鸭子一样叫,那它就是鸭子。
按照鸭子类型哲学,你甚至不用继承任何基类,方法名对得上就可以被当成“鸭子”。
那抽象基类是不是和鸭子类型相矛盾?其实:
- 抽象基类强调 显式的接口约束;
- 鸭子类型强调 行为匹配即可。
在大型项目、多人协作中,显式约束通常更安全、更易维护;
在小脚本、试验性代码中,随意一点无可厚非。
所以:
abc.ABC不是为了推翻鸭子类型,而是在需要“约束和清晰接口”的地方,给你一把更强的工具。
九、使用 abc.ABC 的最佳实践
- 当你写的类“本来就是用来被继承”的时候,用
ABC明确表达意图; - 所有需要子类强制实现的方法,都应该加上
@abstractmethod; - 抽象方法可以带默认实现,但请在 docstring 中说明子类是否需要调用
super(); - 不要滥用:
- 小型脚本、一次性代码,没必要刻意引入
ABC; - 对外暴露的库、框架、长期维护的项目,强烈建议使用。
- 小型脚本、一次性代码,没必要刻意引入
十、总结:一句话记住 abc.ABC
abc.ABC是 Python 用来定义 抽象基类 的基类;- 你通过继承
ABC+@abstractmethod来定义“必须由子类实现”的接口; - 它的作用是:让接口约定更明确,让错误更早暴露,让多人协作更安全。
如果你在写库、写框架、写可复用组件,abc.ABC 是值得熟练掌握的工具。