☰
python 抽象类的使用详解
2026/10/8 16:01:57 网站建设 项目流程

前言


"抽象类"(abstract class)要解决的问题很具体:我想定义一个概念上的"基类",它规定了子类必须实现哪些方法,自己却不提供完整实现,也不应该被直接实例化。


Python 里靠自己写一个方法体里raise NotImplementedError也能模拟出效果,但那只是"软约束"——类照样能被实例化,只有真正调用到那个方法时才会报错。要得到"没实现完就不许实例化"这种硬约束,得用标准库abc模块里的抽象基类(Abstract Base Class,ABC)。


围绕抽象类,有两个常见误解需要先澄清:


第一,以为 ABC 的主要用途是"跑起来时做类型检查"。其实 ABC 最大的价值在接口约定——它在类被实例化的那一刻就拦住"实现不全"的子类,把错误从运行时推迟到更早、更容易定位的位置。


第二,以为用了register()注册的类就跟继承一样了。实际上虚拟子类只是通过issubclass()/isinstance()检查,它不出现在 MRO 里,ABC 定义的方法也不会被它继承到。


本文讲abc.ABC、@abstractmethod的用法与边界,并给出可直接运行的示例。


一、abc.ABC 与 @abstractmethod


abc模块提供辅助类ABC,它本身的元类(metaclass)是ABCMeta。从ABC派生,就能以"最普通"的继承写法创建抽象基类,而不必手动折腾metaclass=。


@abstractmethod是声明抽象方法的装饰器。文档说明:使用它要求所在类的元类是ABCMeta或它的子类;而带有ABCMeta派生元类的类,只要还有未覆盖的抽象方法或抽象属性,就不能被实例化。


# 适用于 Python 3.8+
from abc import ABC, abstractmethod


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

@abstractmethod
def load(self, key):
...


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)


s = MemoryStorage()
s.save("a", 1)
print(s.load("a")) # 1

如果此时直接写Storage(),会立刻抛TypeError: Can't instantiate abstract class Storage with abstract methods load, save。如果子类只实现了save,没有实现load,那么MemoryStorage()同样会抛TypeError,错误信息里会点名还没实现的方法。


注意上面save/load的方法体是...(省略号)。抽象方法可以带实现,子类可以用super()调到它——文档明确说"抽象方法可以通过任何常规的 super 调用机制被调用"。所以下面这种写法是成立的:


# 适用于 Python 3.8+
from abc import ABC, abstractmethod


class Renderer(ABC):
@abstractmethod
def render(self, text):
return text.strip() # 提供一份默认实现


class HtmlRenderer(Renderer):
def render(self, text):
return "<p>" + super().render(text) + "</p>"

二、抽象属性与装饰器顺序


抽象方法也能和@property、@classmethod、@staticmethod组合。文档给出一条硬规则:@abstractmethod要放在最内层(也就是紧贴def的那一行)。


# 适用于 Python 3.8+
from abc import ABC, abstractmethod


class Config(ABC):
@property
@abstractmethod
def path(self):
...

@classmethod
@abstractmethod
def from_env(cls):
...


class FileConfig(Config):
def __init__(self, path):
self._path = path

@property
def path(self):
return self._path

@classmethod
def from_env(cls):
return cls("default.conf")

顺序写反(@abstractmethod在最外层)不会达到预期效果,因为外层的装饰器会先把它包装掉。


另外,早期常用的abc.abstractproperty从 Python 3.3 起就已弃用:@property与@abstractmethod组合已经能正确表达"抽象属性",那个装饰器是多余的。新代码不要再用。


三、虚拟子类与 __subclasshook__


有些类已经存在(甚至是内置类),没法改它的继承关系,但又希望isinstance认它。这时用register():ABC 的元类提供register(subclass),把参数注册成"虚拟子类"。


# 适用于 Python 3.8+
from abc import ABC


class Sized(ABC):
pass


Sized.register(tuple)
print(issubclass(tuple, Sized)) # True
print(isinstance((), Sized)) # True

有个重要限制:虚拟子类不会出现在 MRO 里,ABC 定义的方法也不能被它通过super()调到。所以register()只影响类型检查结果,不带来任何实现复用。


__subclasshook__则更进一步:它是一个必须在 ABC 里定义成classmethod的钩子,让issubclass()的判定可以自定义,从而不必给每个类都手动register()。它应返回True、False或NotImplemented;返回NotImplemented时,检查会回落到常规机制。标准库collections.abc.Iterable就是这么判定"有没有__iter__"的。


下表梳理abc模块的几个常用入口:




名称类型作用



ABC辅助基类元类为ABCMeta,直接继承即可定义 ABC

ABCMeta元类需要自定义元类时使用

@abstractmethod装饰器声明抽象方法,须放在装饰器最内层

register(subclass)方法注册虚拟子类,不影响 MRO

__subclasshook__类方法自定义issubclass()判定

update_abstractmethods(cls)函数(3.10+)重算抽象状态,通常用于类装饰器



四、标准库里现成的 ABC


不是所有场景都要自己定义 ABC。标准库已经准备了一批常用接口,放在collections.abc(Iterable、Sequence、MutableSequence、Mapping等)、numbers、io和importlib.abc里。


# 适用于 Python 3.8+
from collections.abc import Sequence


class MyList:
def __init__(self, data):
self._data = list(data)

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

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


Sequence.register(MyList)
print(isinstance(MyList([1, 2]), Sequence)) # True

需要判断"能不能迭代"时,文档提醒过:isinstance(obj, Iterable)能识别注册过的类和带__iter__的类,但识别不了仅用__getitem__迭代的旧式类。


常见坑点


1. 以为raise NotImplementedError就等于抽象方法


❌


# 适用于 Python 3.8+
class Base:
def run(self):
raise NotImplementedError

Base()能正常创建,错误要等到调用run()才暴露。


✅


# 适用于 Python 3.8+
from abc import ABC, abstractmethod

class Base(ABC):
@abstractmethod
def run(self):
...

Base()当场就抛TypeError。


2. 子类只实现了部分抽象方法就实例化


❌ 以为"实现了主要的那个方法就行",结果一实例化就TypeError,报错还点名漏了哪个方法。


✅ 要么把全部抽象方法补上,要么给子类也加上@abstractmethod让它继续当抽象类。


3. 装饰器顺序写反


❌


# 适用于 Python 3.8+
class C(ABC):
@abstractmethod
@property
def x(self):
...

✅


# 适用于 Python 3.8+
class C(ABC):
@property
@abstractmethod
def x(self):
...

4. 还在用abstractproperty/abstractclassmethod


❌ 从abc导入abstractproperty当新代码用,它们是历史遗留,从 3.3 起已弃用。


✅ 用@property+@abstractmethod组合,或@classmethod+@abstractmethod。


5. 以为register()注册的类能继承 ABC 的方法


❌Sized.register(tuple)之后,以为tuple会多出 ABC 里定义的size()方法。


✅ 虚拟子类只影响isinstance/issubclass,不进 MRO,也拿不到方法实现。


6. 想动态改抽象状态却直接改类属性


❌ 创建类之后再往__abstractmethods__上乱动手脚。


✅ 用abc.update_abstractmethods(cls)(需要 Python 3.10 及以上);该函数假定父类已经更新完,且不会自动更新子类。


7. 用ABCMeta手动配元类却和已有元类冲突


❌ 一个类既想用ABCMeta又想用别的元类,直接写两个metaclass=。


✅ 优先继承abc.ABC;确实需要组合元类时,得自己写一个派生自各方的元类。


8. 把 ABC 当性能敏感处的类型检查手段


❌ 在热点循环里对虚拟子类频繁isinstance检查。


✅ ABC 的虚拟子类判定要走额外逻辑,开销高于对普通类做isinstance;热点路径上优先用鸭子类型或hasattr。


总结




目标做法



定义接口、禁止不完整实例化继承abc.ABC,方法加@abstractmethod

抽象属性@property在外、@abstractmethod在内

复用父类默认实现抽象方法写方法体,子类用super()调用

让已有类通过类型检查ABC.register(类)(3.3+ 返回被注册的类)

自动判定结构重写__subclasshook__(类方法)

用现成接口collections.abc、numbers、io里的 ABC



抽象基类的核心价值是把"接口契约"提前到实例化那一刻强制检查,让缺失实现这类错误尽早暴露。同时要记住它的边界:虚拟子类只改变类型判定,不带来实现复用;抽象方法可以有实现,但装饰器顺序必须对。把这两点拿捏准,abc用起来就没什么意外。





需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询