一文搞懂 Python 的 abc.ABC

5 阅读7分钟

在 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)是用来 定义接口规范 的类,它通常具有这些特点:

  1. 不打算直接被实例化(至少设计上是这样);
  2. 定义了一些必须由子类实现的方法
  3. 在 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

好处立刻显现:

  1. 不能直接实例化抽象类

    a = Animal()  # TypeError: Can't instantiate abstract class Animal with abstract method speak
    
  2. 子类必须实现所有抽象方法才能被实例化

    class Dog(Animal):
        pass
    
    Dog()  # TypeError: Can't instantiate abstract class Dog with abstract method speak
    

    一旦子类漏实现了 speak,在你实例化它的时候就会立刻报错。

  3. 抽象方法在 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_stringvalidate

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))

六、ABCABCMeta 的关系

在内部,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 的最佳实践

  1. 当你写的类“本来就是用来被继承”的时候,用 ABC 明确表达意图
  2. 所有需要子类强制实现的方法,都应该加上 @abstractmethod
  3. 抽象方法可以带默认实现,但请在 docstring 中说明子类是否需要调用 super()
  4. 不要滥用:
    • 小型脚本、一次性代码,没必要刻意引入 ABC
    • 对外暴露的库、框架、长期维护的项目,强烈建议使用。

十、总结:一句话记住 abc.ABC

  • abc.ABC 是 Python 用来定义 抽象基类 的基类;
  • 你通过继承 ABC + @abstractmethod 来定义“必须由子类实现”的接口;
  • 它的作用是:让接口约定更明确,让错误更早暴露,让多人协作更安全。

如果你在写库、写框架、写可复用组件,abc.ABC 是值得熟练掌握的工具。