当前位置: 首页 > news >正文

Python 抽象基类 ABC :从实践到优雅

今天我们来聊聊 Python 中的抽象基类(Abstract Base Class,简称 ABC)。虽然这个概念在 Python 中已经存在很久了,但在日常开发中,很多人可能用得并不多,或者用得不够优雅。

让我们从一个实际场景开始:假设你正在开发一个文件处理系统,需要支持不同格式的文件读写,比如 JSON、CSV、XML 等。

初始版本:简单但不够严谨

我们先来看看最简单的实现方式:

class FileHandler:def read(self, filename):passdef write(self, filename, data):passclass JsonHandler(FileHandler):def read(self, filename):import jsonwith open(filename, 'r') as f:return json.load(f)def write(self, filename, data):import jsonwith open(filename, 'w') as f:json.dump(data, f)class CsvHandler(FileHandler):def read(self, filename):import csvwith open(filename, 'r') as f:return list(csv.reader(f))

这个实现看起来没什么问题,但实际上存在几个隐患:

  1. 无法强制子类实现所有必要的方法
  2. 基类方法的签名(参数列表)可能与子类不一致
  3. 没有明确的接口契约

改进版本:使用抽象基类

让我们引入 abc.ABC 来改进这个设计:

from abc import ABC, abstractmethodclass FileHandler(ABC):@abstractmethoddef read(self, filename: str):"""读取文件内容"""pass@abstractmethoddef write(self, filename: str, data: any):"""写入文件内容"""passclass JsonHandler(FileHandler):def read(self, filename: str):import jsonwith open(filename, 'r') as f:return json.load(f)def write(self, filename: str, data: any):import jsonwith open(filename, 'w') as f:json.dump(data, f)

这个版本引入了两个重要的改进:

  1. 使用 ABCFileHandler 声明为抽象基类
  2. 使用 @abstractmethod 装饰器标记抽象方法

现在,如果我们尝试实例化一个没有实现所有抽象方法的子类,Python 会抛出异常:

# 这个类缺少 write 方法的实现
class BrokenHandler(FileHandler):def read(self, filename: str):return "some data"# 这行代码会抛出 TypeError
handler = BrokenHandler()  # TypeError: Can't instantiate abstract class BrokenHandler with abstract method write

进一步优化:添加类型提示和接口约束

让我们再进一步,添加类型提示和更严格的接口约束:

from abc import ABC, abstractmethod
from typing import Any, List, Dict, Unionclass FileHandler(ABC):@abstractmethoddef read(self, filename: str) -> Union[Dict, List]:"""读取文件内容并返回解析后的数据结构"""pass@abstractmethoddef write(self, filename: str, data: Union[Dict, List]) -> None:"""将数据结构写入文件"""pass@property@abstractmethoddef supported_extensions(self) -> List[str]:"""返回支持的文件扩展名列表"""passclass JsonHandler(FileHandler):def read(self, filename: str) -> Dict:import jsonwith open(filename, 'r') as f:return json.load(f)def write(self, filename: str, data: Dict) -> None:import jsonwith open(filename, 'w') as f:json.dump(data, f)@propertydef supported_extensions(self) -> List[str]:return ['.json']# 使用示例
def process_file(handler: FileHandler, filename: str) -> None:if any(filename.endswith(ext) for ext in handler.supported_extensions):data = handler.read(filename)# 处理数据...handler.write(f'processed_{filename}', data)else:raise ValueError(f"Unsupported file extension for {filename}")

这个最终版本的改进包括:

  1. 添加了类型提示,提高代码的可读性和可维护性
  2. 引入了抽象属性(supported_extensions),使接口更完整
  3. 通过 Union 类型提供了更灵活的数据类型支持
  4. 提供了清晰的文档字符串

使用抽象基类的好处

  1. 接口契约:抽象基类提供了明确的接口定义,任何违反契约的实现都会在运行前被发现。

  2. 代码可读性:通过抽象方法清晰地表明了子类需要实现的功能。

  3. 类型安全:结合类型提示,我们可以在开发时就发现潜在的类型错误。

  4. 设计模式支持:抽象基类非常适合实现诸如工厂模式、策略模式等设计模式。

NotImplementedError 还是 ABC?

很多 Python 开发者会使用 NotImplementedError 来标记需要子类实现的方法:

class FileHandler:def read(self, filename: str) -> Dict:raise NotImplementedError("Subclass must implement read method")def write(self, filename: str, data: Dict) -> None:raise NotImplementedError("Subclass must implement write method")

这种方式看起来也能达到目的,但与 ABC 相比有几个明显的劣势:

  1. 延迟检查:使用 NotImplementedError 只能在运行时发现问题,而 ABC 在实例化时就会检查。
# 使用 NotImplementedError 的情况
class BadHandler(FileHandler):passhandler = BadHandler()  # 这行代码可以执行
handler.read("test.txt")  # 直到这里才会报错# 使用 ABC 的情况
class BadHandler(FileHandler):  # FileHandler 是 ABCpasshandler = BadHandler()  # 直接在这里就会报错
  1. 缺乏语义NotImplementedError 本质上是一个异常,而不是一个接口契约。

  2. IDE 支持:现代 IDE 对 ABC 的支持更好,能提供更准确的代码提示和检查。

不过,NotImplementedError 在某些场景下仍然有其价值:

  1. 当你想在基类中提供部分实现,但某些方法必须由子类覆盖时:
from abc import ABC, abstractmethodclass FileHandler(ABC):@abstractmethoddef read(self, filename: str) -> Dict:passdef process(self, filename: str) -> Dict:data = self.read(filename)if not self._validate(data):raise ValueError("Invalid data format")return self._transform(data)def _validate(self, data: Dict) -> bool:raise NotImplementedError("Subclass should implement validation")def _transform(self, data: Dict) -> Dict:# 默认实现return data

这里,_validate 使用 NotImplementedError 而不是 @abstractmethod,表明它是一个可选的扩展点,而不是必须实现的接口。

代码检查工具的配合

主流的 Python 代码检查工具(pylint、flake8)都对抽象基类提供了良好的支持。

Pylint

Pylint 可以检测到未实现的抽象方法:

# pylint: disable=missing-module-docstring
from abc import ABC, abstractmethodclass Base(ABC):@abstractmethoddef foo(self):passclass Derived(Base):  # pylint: error: Abstract method 'foo' not implementedpass

你可以在 .pylintrc 中配置相关规则:

[MESSAGES CONTROL]
# 启用抽象类检查
enable=abstract-method

Flake8

Flake8 本身不直接检查抽象方法实现,但可以通过插件增强这个能力:

pip install flake8-abstract-base-class

配置 .flake8

[flake8]
max-complexity = 10
extend-ignore = ABC001

metaclass=ABCMeta vs ABC

在 Python 中,有两种方式定义抽象基类:

# 方式 1:直接继承 ABC
from abc import ABC, abstractmethodclass FileHandler(ABC):@abstractmethoddef read(self):pass# 方式 2:使用 metaclass
from abc import ABCMeta, abstractmethodclass FileHandler(metaclass=ABCMeta):@abstractmethoddef read(self):pass

这两种方式在功能上是等价的,因为 ABC 类本身就是用 ABCMeta 作为元类定义的:

class ABC(metaclass=ABCMeta):"""Helper class that provides a standard way to create an ABC usinginheritance."""pass

选择建议:

  1. 推荐使用 ABC

    • 代码更简洁
    • 更符合 Python 的简单直观原则
    • 是 Python 3.4+ 后推荐的方式
  2. 使用 metaclass=ABCMeta 的场景

    • 当你的类已经有其他元类时
    • 需要自定义元类行为时

例如,当你需要组合多个元类的功能时:

class MyMeta(type):def __new__(cls, name, bases, namespace):# 自定义的元类行为return super().__new__(cls, name, bases, namespace)class CombinedMeta(ABCMeta, MyMeta):passclass MyHandler(metaclass=CombinedMeta):@abstractmethoddef handle(self):pass

实践建议

  1. 当你需要确保一组类遵循相同的接口时,使用抽象基类。

  2. 优先使用类型提示,它们能帮助开发者更好地理解代码。

  3. 适当使用抽象属性(@property + @abstractmethod),它们也是接口的重要组成部分。

  4. 在文档字符串中清晰地说明方法的预期行为和返回值。

通过这个实例,我们可以看到抽象基类如何帮助我们写出更加健壮和优雅的 Python 代码。它不仅能够捕获接口违规,还能提供更好的代码提示和文档支持。在下一个项目中,不妨试试用抽象基类来设计你的接口!

相关文章:

Python 抽象基类 ABC :从实践到优雅

今天我们来聊聊 Python 中的抽象基类(Abstract Base Class,简称 ABC)。虽然这个概念在 Python 中已经存在很久了,但在日常开发中,很多人可能用得并不多,或者用得不够优雅。 让我们从一个实际场景开始&…...

Elasticsearch检索方案之一:使用from+size实现分页

前面两篇文章介绍了elasticsearch以及Kibana的安装,检索引擎以及可视化工具都已经安装完成,接下来介绍下如何使用golang的sdk实现简单的分页查询。 1、下载Elastic官方golang sdk 在讲解elasticsearch检索之前,需要先把golang的环境安装好&…...

知识图谱+大模型:打造全新智慧城市底层架构

在数字化时代,智慧城市的建设正迎来新一轮的变革。本文将探讨如何结合知识图谱和大模型技术,构建智慧城市的全新底层架构,以应对日益增长的数据量和复杂性,提升城市管理的智能化水平。 知识图谱:智慧城市的知识库 知识…...

Flutter开发HarmonyOS 鸿蒙App的好处、能力以及把Flutter项目打包成鸿蒙应用

Flutter开发HarmonyOS的好处: Flutter是谷歌公司开发的一款开源、免费的UI框架,可以让我们快速的在Android和iOS上构建高质量App。它最大的特点就是跨平台、以及高性能。 目前 Flutter 已经支持 iOS、Android、Web、Windows、macOS、Linux 的跨平台开发…...

vscode安装fortran插件配置

本章教程,主要介绍如何在vscode上安装fortran插件,以便于使用vscode运行fortran编写的程序。 一、安装插件 首先在插件商店安装这个扩展插件 然后再把Code Runner扩展插件装上 二、下载mingw64 通过网盘分享的文件:mingw64 链接: https://pan.baidu.com/s/1fwS-CwC7dgI...

容器化平台Docker初识

Docker 是一个容器化平台,可以让你打包、分发和运行应用程序。它的核心思想是通过容器技术,让应用程序在任何环境下都能以一致的方式运行。 通俗易懂的理解 快餐盒的比喻: 假设你做了一顿饭(开发了一个应用程序)&#…...

【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】

目录😋 任务描述 相关知识 1、输入数值 2、选择结构语句 3、计算结果并输出 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务:编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。 相关知识 为了完成本…...

简单两步使用ssh配置内网穿透

解决问题:内网主机没有公网IP,无法从外网登录 流程 首先去阿里云租一台最便宜的服务器作为中转服务器 登录中转服务器(cloudserver) ssh [cloudserver] # 开放对应中转服务 ufw allow [remote_port] #remote_port 2222 vim /etc/ssh/sshd_config将对…...

M系列芯片切换镜像源并安装 openJDK17

1. 查找openjdk版本 执行:brew search openjdk,注意:执行命令后,如果得到的结果中没有红框内容,则需要更新一下 brew 更新 brew 分别执行以下命令: cd "$(brew --repo)" export HOMEBREW_API_D…...

图像处理-Ch6-彩色图像处理

Ch6 彩色图像处理 无广告更易阅读&#xff0c;个人博客点此进入<– 文章目录 Ch6 彩色图像处理彩色基础彩色模型(Color models)RGB(red, green, blue)CMY & CMYK(cyan, magenta, yellow/and black)HSI(hue, saturation, intensity)HSV(hue, saturation, value) 颜色空…...

Redis可视化工具 RDM mac安装使用

第一步&#xff1a;https://pan.baidu.com/s/10vpdhw7YfDD7G4yZCGtqQg?at1673701651004将dmg下载 第二部&#xff1a;点击下载的dmg文件进行安装、mac可能会提示&#xff1a; 无法验证此App不包含恶意软件 解决方法&#xff1a; 打开系统偏好设置>安全性与隐私>通用&am…...

单元测试/系统测试/集成测试知识总结

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、单元测试的概念 单元测试是对软件基本组成单元进行的测试&#xff0c;如函数或一个类的方法。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0…...

多目标应用(一):多目标麋鹿优化算法(MOEHO)求解10个工程应用,提供完整MATLAB代码

一、麋鹿优化算法 麋鹿优化算法&#xff08;Elephant Herding Optimization&#xff0c;EHO&#xff09;是2024年提出的一种启发式优化算法&#xff0c;该算法的灵感来源于麋鹿群的繁殖过程&#xff0c;包括发情期和产犊期。在发情期&#xff0c;麋鹿群根据公麋鹿之间的争斗分…...

机器学习和深度学习中的种子设置

一、常见的随机数生成器及其对应的设置方法&#xff1a; Python内置的随机数生成器&#xff1a; import random random.seed(manual_seed)NumPy的随机数生成器&#xff1a; import numpy as np np.random.seed(manual_seed)PyTorch的随机数生成器&#xff1a; import torch tor…...

[手机Linux] 七,NextCloud优化设置

安装完成后在个人设置里发现很多警告&#xff0c;一一消除。 只能一条一条解决了。 关于您的设置有一些错误。 1&#xff0c;PHP 内存限制低于建议值 512 MB。 设置php配置文件&#xff1a; /usr/local/php/etc/php.ini 把里面的&#xff1a; memory_limit 128M 根据你自…...

Ruby+Selenium教程

什么是 Minitest&#xff1f; Minitest 是 Ruby 的测试框架&#xff0c;提供一整套测试工具。它运行速度快&#xff0c;支持 TDD、BDD、模拟和基准测试 以下是使用Ruby、Selenium WebDriver和Minitest 的脚本&#xff0c;用于断言 Restful Booker Platform 的“页面标题”等于…...

【论文阅读笔记】Learning to sample

Learning to sample 前沿引言方法问题声明S-NET匹配ProgressiveNet: sampling as ordering 实验分类检索重建 结论附录 前沿 这是一篇比较经典的基于深度学习的点云下采样方法 核心创新点&#xff1a; 首次提出了一种学习驱动的、任务特定的点云采样方法引入了两种采样网络&…...

边缘计算收益稳定

要使自己的PCDN&#xff08;Personal Content Delivery Network&#xff0c;个人内容分发网络&#xff09;收益更稳定&#xff0c;可以从以下几个方面进行努力&#xff1a; 一、选择合适的PCDN平台 平台稳定性&#xff1a;选择技术成熟、稳定性高的PCDN平台&#xff0c;确保内…...

域名和服务器是什么?域名和服务器是什么关系?

在互联网的生态系统中&#xff0c;域名和服务器是两个至关重要的组成部分。它们共同构成了我们访问网站和使用在线服务的基础。那么域名和服务器是什么?域名和服务器是什么关系? 1、域名的概念 域名是互联网中用于标识特定地址的一种文字形式。它是用户访问网站时输入的易记…...

IBatis和MyBatis在细节上的不同有哪些

iBatis 和 MyBatis 都是流行的 Java 持久化框架&#xff0c;用于简化数据库交互。MyBatis 是从 iBatis 演化而来&#xff0c;MyBatis 在 iBatis 的基础上做了很多改进和优化&#xff0c;因此两者在设计和功能上存在一些差异。以下是它们在细节上的主要区别&#xff1a; 1. 框架…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

nnUNet V2修改网络——暴力替换网络为UNet++

更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

Spring Boot + MyBatis 集成支付宝支付流程

Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例&#xff08;电脑网站支付&#xff09; 1. 添加依赖 <!…...

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁

赛门铁克威胁猎手团队最新报告披露&#xff0c;数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据&#xff0c;严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能&#xff0c;但SEMR…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...

Linux中INADDR_ANY详解

在Linux网络编程中&#xff0c;INADDR_ANY 是一个特殊的IPv4地址常量&#xff08;定义在 <netinet/in.h> 头文件中&#xff09;&#xff0c;用于表示绑定到所有可用网络接口的地址。它是服务器程序中的常见用法&#xff0c;允许套接字监听所有本地IP地址上的连接请求。 关…...