Python的ABC库探索:能不能在系统设计之初就定义好所有抽象类?

21 阅读12分钟

Python 里的 ABC 指的是 Abstract Base Class,抽象基类。它的核心作用是:定义一类对象应该具备哪些接口或行为,但不一定提供完整实现。换句话说,ABC 像是一份“类的契约”:子类必须实现指定的方法,否则不能被实例化。

下面我们从概念、基本用法、典型示例、进阶机制和实际应用场景几个角度来系统梳理。


1. ABC 是什么?

在 Python 中,ABC 通常由标准库中的 abc 模块提供。

from abc import ABC, abstractmethod

其中:

  • ABC:抽象基类的基类。
  • abstractmethod:用于标记抽象方法。
  • 抽象类:包含至少一个抽象方法的类。
  • 抽象方法:只声明接口,要求子类必须实现的方法。

简单理解

假设你要设计一个“动物”系统:

  • 所有动物都应该会“叫”。
  • 但是不同动物叫声不同。
  • 所以基类 Animal 不应该直接规定具体叫声。
  • 它只要求子类必须实现 speak() 方法。

这时就可以使用 ABC。


2. 最基础的 ABC 示例

下面是一个最经典的抽象基类例子。

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):
    def speak(self):
        return "汪汪"


class Cat(Animal):
    def speak(self):
        return "喵喵"


dog = Dog()
cat = Cat()

print(dog.speak())
print(cat.speak())

输出:

汪汪
喵喵

这里的 Animal 是抽象基类。

它定义了一个抽象方法:

@abstractmethod
def speak(self):
    pass

这表示:任何继承 Animal 的子类,都必须实现 speak() 方法。


3. 如果子类没有实现抽象方法会怎样?

如果子类没有实现抽象方法,那么这个子类依然是抽象类,不能被实例化。

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):
    pass


dog = Dog()

运行后会报错:

TypeError: Can't instantiate abstract class Dog with abstract method speak

意思是:

Dog 这个类继承了抽象类 Animal,但是没有实现 speak() 方法,所以不能创建对象。


4. ABC 的主要作用

ABC 的价值不只是“防止实例化”,更重要的是它能帮助你设计更清晰的程序结构。

4.1 定义统一接口

比如你要设计多个支付方式:

  • 支付宝支付
  • 微信支付
  • 银行卡支付

它们都应该有一个统一的 pay() 方法。

from abc import ABC, abstractmethod


class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        pass


class Alipay(Payment):
    def pay(self, amount):
        print(f"使用支付宝支付 {amount} 元")


class WechatPay(Payment):
    def pay(self, amount):
        print(f"使用微信支付 {amount} 元")


class BankCardPay(Payment):
    def pay(self, amount):
        print(f"使用银行卡支付 {amount} 元")


def checkout(payment: Payment, amount):
    payment.pay(amount)


checkout(Alipay(), 100)
checkout(WechatPay(), 200)
checkout(BankCardPay(), 300)

输出:

使用支付宝支付 100 元
使用微信支付 200 元
使用银行卡支付 300

这里的 Payment 就是一个抽象基类,它规定所有支付类都必须实现:

pay(amount)

这样你的业务代码就可以面向统一接口编程,而不需要关心具体是哪种支付方式。


5. 抽象类中可以有普通方法吗?

可以。

抽象基类不仅可以有抽象方法,也可以包含普通方法、属性、初始化逻辑等。

from abc import ABC, abstractmethod


class Shape(ABC):
    def __init__(self, name):
        self.name = name

    def describe(self):
        print(f"这是一个图形:{self.name}")

    @abstractmethod
    def area(self):
        pass


class Circle(Shape):
    def __init__(self, radius):
        super().__init__("圆形")
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2


circle = Circle(5)
circle.describe()
print(circle.area())

输出:

这是一个图形:圆形
78.53975

这里:

  • describe() 是普通方法,子类可以直接继承使用。
  • area() 是抽象方法,子类必须实现。
  • Shape 不能直接实例化。

6. 抽象方法可以有默认实现吗?

可以。

很多人以为抽象方法里面只能写 pass,其实不是。抽象方法也可以有默认实现,子类仍然必须显式覆盖它,但可以通过 super() 调用父类逻辑。

from abc import ABC, abstractmethod


class BaseLogger(ABC):
    @abstractmethod
    def log(self, message):
        print(f"[基础日志] {message}")


class FileLogger(BaseLogger):
    def log(self, message):
        super().log(message)
        print(f"写入文件:{message}")


logger = FileLogger()
logger.log("系统启动")

输出:

[基础日志] 系统启动
写入文件:系统启动

注意:

即使 BaseLogger.log() 有具体实现,只要它被 @abstractmethod 标记,子类就必须重写它。


7. 抽象属性:@property@abstractmethod

ABC 不仅可以约束方法,也可以约束属性。

from abc import ABC, abstractmethod


class Employee(ABC):
    @property
    @abstractmethod
    def salary(self):
        pass


class Developer(Employee):
    def __init__(self, base_salary):
        self._salary = base_salary

    @property
    def salary(self):
        return self._salary


dev = Developer(20000)
print(dev.salary)

输出:

20000

这里要求所有 Employee 子类必须提供 salary 属性。


8. 抽象类方法和抽象静态方法

ABC 也可以和 @classmethod@staticmethod 一起使用。

8.1 抽象类方法

from abc import ABC, abstractmethod


class Database(ABC):
    @classmethod
    @abstractmethod
    def connect(cls, url):
        pass


class MySQLDatabase(Database):
    @classmethod
    def connect(cls, url):
        print(f"MySQL 连接到:{url}")


MySQLDatabase.connect("mysql://localhost:3306/test")

输出:

MySQL 连接到:mysql://localhost:3306/test

8.2 抽象静态方法

from abc import ABC, abstractmethod


class Validator(ABC):
    @staticmethod
    @abstractmethod
    def validate(value):
        pass


class EmailValidator(Validator):
    @staticmethod
    def validate(value):
        return "@" in value


print(EmailValidator.validate("test@example.com"))
print(EmailValidator.validate("invalid-email"))

输出:

True
False

9. ABC 和 isinstance() / issubclass()

ABC 可以用于类型判断。

from abc import ABC, abstractmethod


class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        pass


class Alipay(Payment):
    def pay(self, amount):
        print(f"支付宝支付 {amount} 元")


print(isinstance(Alipay(), Payment))
print(issubclass(Alipay, Payment))

输出:

True
True

这说明:

  • Alipay()Payment 的实例。
  • AlipayPayment 的子类。

10. 虚拟子类:register()

ABC 还有一个较特殊的功能:注册虚拟子类

即使某个类没有显式继承抽象基类,也可以被注册为它的“虚拟子类”。

from abc import ABC, abstractmethod


class Plugin(ABC):
    @abstractmethod
    def run(self):
        pass


class ExternalPlugin:
    def run(self):
        print("外部插件运行")


Plugin.register(ExternalPlugin)

plugin = ExternalPlugin()

print(isinstance(plugin, Plugin))
print(issubclass(ExternalPlugin, Plugin))

plugin.run()

输出:

True
True
外部插件运行

注意:

ExternalPlugin 并没有继承 Plugin

class ExternalPlugin:
    ...

但经过:

Plugin.register(ExternalPlugin)

之后,它会被认为是 Plugin 的虚拟子类。

重要提醒

register() 不会强制检查方法是否真的存在。

例如:

class BadPlugin:
    pass


Plugin.register(BadPlugin)

print(isinstance(BadPlugin(), Plugin))

这仍然可能输出:

True

BadPlugin 并没有实现 run() 方法。

所以 register() 更像是一种类型声明,而不是严格校验机制。


11. __subclasshook__:自定义子类判断逻辑

__subclasshook__ 可以让你自定义 issubclass() 的判断规则。

例如,只要一个类有 run() 方法,就认为它是 Runnable 的子类。

from abc import ABC, abstractmethod


class Runnable(ABC):
    @abstractmethod
    def run(self):
        pass

    @classmethod
    def __subclasshook__(cls, subclass):
        if cls is Runnable:
            if any("run" in base.__dict__ for base in subclass.__mro__):
                return True
        return NotImplemented


class Task:
    def run(self):
        print("任务运行")


class Person:
    def walk(self):
        print("人走路")


print(issubclass(Task, Runnable))
print(issubclass(Person, Runnable))

输出:

True
False

这里 Task 没有继承 Runnable,但是它有 run() 方法,因此被认为是 Runnable 的子类。


12. Python 内置的 ABC:collections.abc

Python 标准库中已经提供了很多常用的抽象基类,位于 collections.abc 中。

常见的有:

ABC含义典型方法
Iterable可迭代对象__iter__()
Iterator迭代器__iter__()__next__()
Sequence序列__getitem__()__len__()
MutableSequence可变序列append()insert()
Mapping映射类型keys()items()__getitem__()
MutableMapping可变映射__setitem__()__delitem__()
Set集合__contains__()__iter__()__len__()
Callable可调用对象__call__()

这些 ABC 主要用于判断对象是否符合某种协议。

示例:判断对象是否可迭代

from collections.abc import Iterable


print(isinstance([1, 2, 3], Iterable))
print(isinstance("hello", Iterable))
print(isinstance(123, Iterable))

输出:

True
True
False

列表和字符串都可以迭代,而整数不能。


13. 自定义一个类似列表的类

下面我们用 collections.abc.MutableSequence 实现一个自定义列表类型。

from collections.abc import MutableSequence


class MyList(MutableSequence):
    def __init__(self):
        self._items = []

    def __len__(self):
        return len(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        self._items[index] = value

    def __delitem__(self, index):
        del self._items[index]

    def insert(self, index, value):
        self._items.insert(index, value)


my_list = MyList()

my_list.append(10)
my_list.append(20)
my_list.append(30)

print(len(my_list))
print(my_list[0])
print(list(my_list))

my_list[1] = 200
print(list(my_list))

del my_list[0]
print(list(my_list))

输出:

3
10
[10, 20, 30]
[10, 200, 30]
[200, 30]

这里继承 MutableSequence 后,只要实现几个核心方法:

__len__
__getitem__
__setitem__
__delitem__
insert

就可以自动获得很多列表行为,例如:

append()
remove()
reverse()
extend()
pop()

这就是 ABC 的一个强大之处:它既定义接口,也能提供部分通用实现。


14. ABC 与普通继承的区别

ABC 本质上也是继承,但它更强调“接口约束”。

对比项普通基类抽象基类 ABC
是否能直接实例化通常可以有抽象方法时不可以
是否强制子类实现方法不强制强制
主要目的代码复用接口规范 + 代码复用
常用模块不需要额外模块abc
常见场景父类提供通用逻辑框架、插件、支付、存储、驱动等

普通继承更像是:

“子类复用父类的代码。”

ABC 更像是:

“子类必须遵守父类定义的规范。”


15. ABC 与鸭子类型的关系

Python 是一门强调 鸭子类型 的语言。

所谓鸭子类型,就是:

如果一个对象走起来像鸭子,叫起来像鸭子,那它就可以被当作鸭子。

也就是说,Python 通常不关心对象到底是什么类型,只关心它有没有需要的方法。

例如:

class Dog:
    def speak(self):
        print("汪汪")


class Robot:
    def speak(self):
        print("机器人发声")


def make_it_speak(obj):
    obj.speak()


make_it_speak(Dog())
make_it_speak(Robot())

即使 DogRobot 没有共同父类,只要它们都有 speak() 方法,就可以正常工作。

那为什么还需要 ABC?

因为在较大的项目中,完全依赖鸭子类型可能会带来一些问题:

  • 接口不统一。
  • 子类漏实现方法时,错误可能到运行很久之后才暴露。
  • 阅读代码时不容易知道某类必须实现哪些能力。
  • 框架或插件系统需要明确规范。

ABC 就是在动态语言中提供一种更明确的接口约束方式。


16. ABC 与 typing.Protocol 的区别

现代 Python 中,除了 ABC,还有一个常用概念是 Protocol

它们都可以表达“接口”,但关注点不同。

对比项ABCProtocol
所在模块abctyping
主要用途运行时接口约束静态类型检查
是否要求显式继承通常需要不一定
能否阻止实例化可以通常不用于这个目的
是否影响 isinstance()可以默认不行,需 @runtime_checkable
适合场景框架基类、插件系统、运行时检查类型标注、mypy/pyright 检查

ABC 示例

from abc import ABC, abstractmethod


class Sender(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        pass


class EmailSender(Sender):
    def send(self, message: str) -> None:
        print(f"发送邮件:{message}")

Protocol 示例

from typing import Protocol


class Sender(Protocol):
    def send(self, message: str) -> None:
        ...


class EmailSender:
    def send(self, message: str) -> None:
        print(f"发送邮件:{message}")


def notify(sender: Sender):
    sender.send("你好")

Protocol 例子中,EmailSender 没有继承 Sender,但只要它实现了 send() 方法,静态类型检查器就会认为它符合 Sender 协议。


17. 一个更完整的实战例子:文件存储系统

假设你要设计一个存储系统,支持:

  • 本地文件存储
  • 云存储
  • 内存存储

它们都应该支持:

  • 保存数据
  • 读取数据
  • 删除数据

可以这样设计:

from abc import ABC, abstractmethod


class Storage(ABC):
    @abstractmethod
    def save(self, key, value):
        pass

    @abstractmethod
    def load(self, key):
        pass

    @abstractmethod
    def delete(self, key):
        pass


class MemoryStorage(Storage):
    def __init__(self):
        self._data = {}

    def save(self, key, value):
        self._data[key] = value

    def load(self, key):
        return self._data.get(key)

    def delete(self, key):
        self._data.pop(key, None)


class LocalFileStorage(Storage):
    def save(self, key, value):
        with open(key, "w", encoding="utf-8") as file:
            file.write(value)

    def load(self, key):
        with open(key, "r", encoding="utf-8") as file:
            return file.read()

    def delete(self, key):
        import os

        if os.path.exists(key):
            os.remove(key)


def backup(storage: Storage, key, value):
    storage.save(key, value)
    print("保存成功")
    print("读取结果:", storage.load(key))


memory_storage = MemoryStorage()
backup(memory_storage, "username", "Alice")

file_storage = LocalFileStorage()
backup(file_storage, "user.txt", "Bob")

这个设计的好处是:

  • backup() 不依赖具体存储类型。
  • 新增存储类型时,只需要继承 Storage 并实现方法。
  • 如果漏实现方法,Python 会直接报错。
  • 代码结构清晰,扩展性更强。

18. 常见错误和注意事项

18.1 忘记继承 ABC

有时你可能写了 @abstractmethod,但没有继承 ABC

from abc import abstractmethod


class Animal:
    @abstractmethod
    def speak(self):
        pass

这不是推荐写法。

更规范的是:

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

18.2 子类方法名写错

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):
    def speek(self):
        return "汪汪"


dog = Dog()

这里子类写成了:

def speek(self):

而不是:

def speak(self):

所以会报错:

TypeError: Can't instantiate abstract class Dog with abstract method speak

ABC 可以帮助你提前发现这种问题。

18.3 抽象属性装饰器顺序

推荐写法:

@property
@abstractmethod
def name(self):
    pass

完整例子:

from abc import ABC, abstractmethod


class User(ABC):
    @property
    @abstractmethod
    def name(self):
        pass

19. 什么时候应该使用 ABC?

适合使用 ABC 的场景包括:

  • 你在写框架或库。
  • 你需要定义插件接口。
  • 多个类有共同能力,并且必须实现某些方法。
  • 希望子类漏实现方法时尽早报错。
  • 希望使用 isinstance()issubclass() 做接口判断。
  • 希望基类提供部分公共逻辑,同时要求子类补充关键实现。

例如:

class DatabaseDriver(ABC):
    @abstractmethod
    def connect(self):
        pass

    @abstractmethod
    def execute(self, sql):
        pass

    @abstractmethod
    def close(self):
        pass

这类场景很适合 ABC。


20. 什么时候不一定需要 ABC?

如果项目很小,或者只是临时写几个类,ABC 可能显得有点重。

比如:

def print_name(obj):
    print(obj.name)

只要传进来的对象有 name 属性即可,不一定非要定义一个抽象基类。

Python 中很多时候直接使用鸭子类型就足够了。


21. 核心总结

Python 的 ABC 是一种用于定义接口规范的机制。

它主要解决的问题是:

  • 规定子类必须实现哪些方法
  • 防止不完整的类被实例化
  • 提升大型项目中的代码可读性和可维护性
  • 支持运行时类型判断
  • 提供部分公共逻辑,让子类复用

最常用写法是:

from abc import ABC, abstractmethod


class BaseClass(ABC):
    @abstractmethod
    def required_method(self):
        pass


class ChildClass(BaseClass):
    def required_method(self):
        print("实现抽象方法")

一句话记忆:

ABC 就是 Python 中用来定义“子类必须遵守的接口契约”的工具。