[python] python抽象基类使用总结
在Python中,抽象基类是一类特殊的类,它不能被实例化,主要用于作为基类被其他子类继承。抽象基类的核心作用是为一组相关的子类提供统一的蓝图或接口规范,明确规定子类必须实现的方法,从而增强代码的规范性和可维护性。Python通过abc(Abstract Base Classes)模块提供了对抽象基类的支持,允许开发者创建和使用抽象基类。
抽象基类的主要特点和用途包括:
- 接口一致性:通过定义抽象方法,抽象基类确保所有子类必须实现这些方法,从而保证子类具有一致的接口;
- 避免不完整实现:若子类未实现抽象基类中的所有抽象方法,该子类仍会被视为抽象基类,无法实例化;
- 提高代码可维护性:清晰定义的接口使代码结构更清晰,便于团队协作和系统扩展。
Python要创建抽象基类,需继承abc.ABC并使用@abstractmethod装饰器标记必须被重写的方法。在实际应用中,抽象基类广泛用于以下场景:
- 框架设计:定义接口规范,强制子类实现特定方法,确保框架扩展的一致性;
- 插件系统:规定插件必须实现的通用接口,方便系统动态加载第三方模块;
- 团队协作:明确模块间的交互契约,避免开发人员遗漏关键方法的实现;
- 代码复用:通过抽象基类封装通用逻辑,子类只需实现差异化部分;
- 类型检查:结合
isinstance()进行运行时类型验证,确保对象符合预期接口; - 复杂系统架构:构建多层次的类继承体系,清晰划分各层级的职责边界。
通过合理使用抽象基类,开发者可以创建更健壮、更具扩展性的代码架构,同时减少因接口不一致导致的错误。

1 使用入门
创建基础抽象基类
以下代码展示了Python中面向对象编程的几个重要概念:
- 抽象基类 (Abstract Base Class, ABC)
- Animal(ABC) 是一个抽象基类,继承自ABC(需要从
abc模块导入)。 - 作用:抽象基类用于定义一组方法的接口规范,但不能被直接实例化。它要求子类必须实现这些方法,否则会报错。
- 关键点:
抽象基类通过@abstractmethod装饰器标记抽象方法。
如果子类没有实现所有抽象方法,Python会阻止子类的实例化。
- Animal(ABC) 是一个抽象基类,继承自ABC(需要从
- 抽象方法 (
@abstractmethod)- move()和sound()是抽象方法,用
@abstractmethod装饰。 - 作用:
- 强制子类必须实现这些方法。
- 定义了一个统一的接口规范(例如所有动物都必须能“移动”和“发声”)。
- 关键点:
- 抽象方法只有声明,没有实现(用
pass关键字占位)。 - 如果子类不实现这些方法,尝试实例化时会引发
TypeError。
- 抽象方法只有声明,没有实现(用
- move()和sound()是抽象方法,用
- 继承 (Bird和Fish继承Animal)
- Bird和Fish是Animal的子类。子类必须实现父类中所有的抽象方法。
- 不同子类对同一方法有不同的实现。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def move(self):
pass
@abstractmethod
def sound(self):
pass
class Bird(Animal):
def __init__(self, name):
self.name = name
def move(self):
return f"{self.name} is flying"
def sound(self):
return "Chirp chirp"
class Fish(Animal):
def __init__(self, name):
self.name = name
def move(self):
return f"{self.name} is swimming"
def sound(self):
return "Blub blub"
# 创建实例并调用方法
sparrow = Bird("Sparrow")
print(sparrow.move()) # 输出: Sparrow is flying
print(sparrow.sound()) # 输出: Chirp chirp
salmon = Fish("Salmon")
print(salmon.move()) # 输出: Salmon is swimming
print(salmon.sound()) # 输出: Blub blub
# animal = Animal() # 尝试实例化抽象基类会引发TypeError
Sparrow is flying
Chirp chirp
Salmon is swimming
Blub blub
抽象属性Abstract property
以下示例将name方法声明为抽象属性,要求所有继承Person的子类必须实现这个属性。使用@property表示这应该是一个属性而不是普通方法。通过@property装饰器,用于将类的方法转换为"属性",使得可以像访问属性一样访问方法,而不需要使用调用语法(即不需要加括号)。注意子类必须同样使用@property装饰器来实现该属性。
使用@property的优势在于能够控制访问权限,定义只读属性,防止属性被意外修改。例如:
emp = Employee("Sarah", "Johnson")
emp.name = "Alice" # 会报错,AttributeError: can't set attribute
示例代码如下:
from abc import ABC, abstractmethod
class Person(ABC):
"""抽象基类,表示一个人"""
@property
@abstractmethod
def name(self) -> str:
"""获取人的姓名"""
pass
@abstractmethod
def speak(self) -> str:
"""人说话的抽象方法"""
pass
class Employee(Person):
"""表示公司员工的类"""
def __init__(self, first_name: str, last_name: str):
"""
初始化员工对象
Args:
first_name: 员工的名字
last_name: 员工的姓氏
"""
if not first_name or not last_name:
raise ValueError("名字和姓氏不能为空")
self._full_name = f"{first_name} {last_name}"
@property
def name(self) -> str:
"""获取员工的全名"""
return self._full_name
def speak(self) -> str:
"""员工打招呼的具体实现"""
return f"Hello, my name is {self.name}"
# 创建员工实例并测试
emp = Employee("Sarah", "Johnson")
# emp.name = "Alice" # 会报错,AttributeError: can't set attribute
print(emp.name) # 输出: Sarah Johnson
print(emp.speak()) # 输出: Hello, my name is Sarah Johnson
Sarah Johnson
Hello, my name is Sarah Johnson
带类方法的抽象基类
当方法不需要访问或修改实例状态(即不依赖self属性)时,使用类方法可以避免创建不必要的实例,从而提高效率并简化代码。
from abc import ABC, abstractmethod
# 抽象基类:包裹
class Package(ABC):
@classmethod
@abstractmethod
def pack(cls, items):
pass
@classmethod
@abstractmethod
def unpack(cls, packed_items):
pass
# 具体实现类:纸箱包裹
class CardboardBox(Package):
@classmethod
def pack(cls, items):
return f"纸箱包裹: {items}"
@classmethod
def unpack(cls, packed_items):
return packed_items.replace("纸箱包裹: ", "")
# 具体实现类:泡沫包裹
class FoamPackage(Package):
@classmethod
def pack(cls, items):
return f"泡沫包裹: {items}"
@classmethod
def unpack(cls, packed_items):
return packed_items.replace("泡沫包裹: ", "")
# 打包物品
cardboard_packed = CardboardBox.pack(["衣服", "鞋子"])
foam_packed = FoamPackage.pack(["玻璃制品", "陶瓷"])
# 解包物品,使用不同的对象
cardboard_items = CardboardBox.unpack(cardboard_packed)
foam_items = FoamPackage.unpack(foam_packed)
# 输出结果
print("解包后 - 纸箱:", cardboard_items)
print("解包后 - 泡沫:", foam_items)
解包后 - 纸箱: ['衣服', '鞋子']
解包后 - 泡沫: ['玻璃制品', '陶瓷']
带有具体方法的抽象基类
该示例呈现了一个兼具抽象方法与具体方法(实例方法)的抽象基类。抽象基类中既包含子类必须实现的抽象方法,也有提供共享功能的具体方法。operate具体方法界定了 “启动→运行→停止” 的通用操作流程,而具体实现则由子类负责。此模式让抽象基类能够把控算法结构,同时将细节实现延迟至子类。这不仅提升了代码的可维护性,还便于在不改动现有代码结构的前提下添加摩托车、飞机等新的交通工具。
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def start(self):
pass
@abstractmethod
def stop(self):
pass
def operate(self):
self.start()
print("Vehicle is in operation...")
self.stop()
class Car(Vehicle):
def start(self):
print("Starting car engine")
def stop(self):
print("Turning off car engine")
class Bicycle(Vehicle):
def start(self):
print("Starting to pedal bicycle")
def stop(self):
print("Applying bicycle brakes")
# 使用示例
car = Car()
car.operate()
bicycle = Bicycle()
bicycle.operate()
Starting car engine
Vehicle is in operation...
Turning off car engine
Starting to pedal bicycle
Vehicle is in operation...
Applying bicycle brakes
非显式继承
这个示例展示了如何在不进行显式继承的情况下,将类注册为抽象基类的虚拟子类。register方法允许声明某个类实现了抽象基类,却无需直接继承该基类。
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def move(self):
pass
class Car:
def move(self):
return "Driving on the road!"
# 注册Car类为Vehicle的虚拟子类
Vehicle.register(Car)
car = Car()
# 输出: True(因为 Car 已被注册为 Vehicle 的虚拟子类)
print(isinstance(car, Vehicle))
# 输出: True(同上)
print(issubclass(Car, Vehicle))
print(car.move())
True
True
Driving on the road!
一般来说虚拟子类必须实现所有的抽象方法,但这种检查要等到尝试调用这些方法时才会进行。在处理无法修改的类或者使用鸭子类型时,这种方式十分实用。注意鸭子类型是Python中的一个重要编程概念,源自一句谚语:"如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子"(If it walks like a duck and quacks like a duck, then it must be a duck)。
在Python中,鸭子类型指的是:
- 不关注对象的类型本身,而是关注对象具有的行为(方法或属性);
- 只要一个对象具有所需的方法或属性,它就可以被当作特定类型使用,而不需要显式地继承或声明。
示例代码如下,duck_test函数并不关心传入的对象是Duck还是Person,只要该对象拥有quack和walk方法,就可以正常调用。
class Duck:
def quack(self):
print("嘎嘎嘎")
def walk(self):
print("摇摇摆摆地走")
class Person:
def quack(self):
print("人类模仿鸭子叫")
def walk(self):
print("人类两条腿走路")
def duck_test(duck):
duck.quack()
duck.walk()
# 创建Duck和Person的实例
donald = Duck()
john = Person()
# 调用duck_test函数
duck_test(donald)
duck_test(john)
嘎嘎嘎
摇摇摆摆地走
人类模仿鸭子叫
人类两条腿走路
多重继承
以下例子展示了抽象基类在多重继承中的应用。通过多重继承,可以将多个抽象基类组合,创建出能实现多种接口的类。例如,RadioRecorder类同时继承了Listenable和Recordable两个抽象基类,并实现了它们的所有抽象方法。这种方式既满足了严格的实现要求,又能灵活地定义接口。
from abc import ABC, abstractmethod
# 定义可收听的抽象接口
class Listenable(ABC):
@abstractmethod
def listen(self):
pass
# 定义可录制的抽象接口
class Recordable(ABC):
@abstractmethod
def record(self, content):
pass
# 收音机录音机实现类
class RadioRecorder(Listenable, Recordable):
def __init__(self, channel):
self.channel = channel # 收音机频道
self.recording = [] # 录制内容存储
def listen(self):
return f"Listening to {self.channel}"
def record(self, content):
self.recording.append(content)
return f"Recording '{content}' on {self.channel}"
# 使用示例
radio = RadioRecorder("FM 98.6")
print(radio.listen())
print(radio.record("Music"))
Listening to FM 98.6
Recording 'Music' on FM 98.6
如果两个抽象基类有相同的方法名,会导致方法冲突。 Python中,当多重继承的父类存在同名方法时,调用顺序由方法解析顺序。例如以下代码中抽象基类都存在change方法,在子类change方法内部可以根据参数类型分别处理不同的逻辑来避免冲突:
from abc import ABC, abstractmethod
# 定义可收听的抽象接口
class Listenable(ABC):
@abstractmethod
def listen(self):
pass
@abstractmethod
def change(self, channel):
"""切换收听频道"""
pass
# 定义可录制的抽象接口
class Recordable(ABC):
@abstractmethod
def record(self, content):
pass
@abstractmethod
def change(self, format):
"""切换录制格式"""
pass
# 收音机录音机实现类
class RadioRecorder(Listenable, Recordable):
def __init__(self, channel, format):
self.channel = channel # 收音机频道
self.format = format # 录制格式
self.recording = [] # 录制内容存储
def listen(self):
return f"Listening to {self.channel}"
def record(self, content):
self.recording.append(content)
return f"Recording '{content}' in {self.format}"
# 解决方法冲突
def change(self, param):
# 根据参数类型判断调用哪个父类的change方法
if isinstance(param, str): # 假设字符串参数是频道
self.channel = param
return f"Changed channel to {param}"
elif isinstance(param, int): # 假设整数参数是格式编号
formats = ["MP3", "WAV", "FLAC"]
if 0 <= param < len(formats):
self.format = formats[param]
return f"Changed format to {self.format}"
return "Invalid parameter"
# 使用示例
radio = RadioRecorder("FM 98.6", "MP3")
print(radio.listen())
print(radio.record("Music"))
print(radio.change("AM 1070"))
print(radio.change(2))
Listening to FM 98.6
Recording 'Music' in MP3
Changed channel to AM 1070
Changed format to FLAC
此外在Python的多重继承中,方法解析顺序(Method Resolution Order, MRO)是一个重要的概念,它决定了当子类调用一个方法时,Python解释器会按照什么顺序在父类中查找这个方法。MRO的规则:
- 深度优先,从左到右:Python会先检查第一个父类及其祖先,然后再检查第二个父类及其祖先,以此类推。
- C3线性化算法:Python2.3之后使用C3线性化算法计算MRO,确保每个类只被访问一次,解决了经典类中的 "菱形继承" 问题。
以上述代码为例,RadioRecorder继承自Listenable和Recordable。Listenable排在Recordable前面,这意味着当两个父类有同名方法时,Listenable的方法会被优先调用。因此这里的MRO顺序是:RadioRecorder -> Listenable -> Recordable -> object。这意味着:
- 当调用radio.change()时,Python 会先在RadioRecorder中查找change方法。
- 如果没找到,会在Listenable中查找。
- 如果还没找到,会在Recordable中查找。
- 最后查找object类(所有类的基类)。
可以使用__mro__属性或mro()方法查看类的MRO顺序:
print(RadioRecorder.__mro__)
(<class '__main__.RadioRecorder'>, <class '__main__.Listenable'>, <class '__main__.Recordable'>, <class 'abc.ABC'>, <class 'object'>)
2 参考
[python] python抽象基类使用总结的更多相关文章
- python之抽象基类
抽象基类特点 1.不能够实例化 2.在这个基础的类中设定一些抽象的方法,所有继承这个抽象基类的类必须覆盖这个抽象基类里面的方法 思考 既然python中有鸭子类型,为什么还要使用抽象基类? 一是我们在 ...
- Python抽象基类之声明协议
抽象基类之--声明协议 上回讲了Python中抽象基类的大概,相信大家对abcmeta以及什么是抽象基类已经有所了解.传送门 现在我们来讲讲抽象基类的另一个常用用法--声明协议 所谓声明协议,有点像J ...
- PythonI/O进阶学习笔记_3.2面向对象编程_python的继承(多继承/super/MRO/抽象基类/mixin模式)
前言: 本篇相关内容分为3篇多态.继承.封装,这篇为第二篇 继承. 本篇内容围绕 python基础教程这段: 在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法.使 ...
- Fluent_Python_Part4面向对象,11-iface-abc,协议(接口),抽象基类
第四部分第11章,接口:从协议到抽象基类(重点讲抽象基类) 接口就是实现特定角色的方法集合. 严格来说,协议是非正式的接口(只由文档约束),正式接口会施加限制(抽象基类对接口一致性的强制). 在Pyt ...
- python面对对象编程---------6:抽象基类
抽象基本类的几大特点: 1:要定义但是并不完整的实现所有方法 2:基本的意思是作为父类 3:父类需要明确表示出那些方法的特征,这样在写子类时更加简单明白 用抽象基本类的地方: 1:用作父类 2:用作检 ...
- 流畅python学习笔记:第十一章:抽象基类
__getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...
- Python 接口:从协议到抽象基类
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 抽象基类的常见用途:实现接口时作为超类使用.然后,说明抽象基类如何检查 ...
- guxh的python笔记七:抽象基类
1,鸭子类型和白鹅类型 1.1,白鹅类型 白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__ ...
- Python中的对象行为与特殊方法(二)类型检查与抽象基类
类型检查 创建类的实例时,该实例的类型为类本身: class Foo(object): pass f = Foo() 要测试实例是否属于某个类,可以使用type()内置函数: >>> ...
- 【Python】【元编程】【从协议到抽象基类】
"""class Vector2d: typecode = 'd' def __init__(self,x,y): self.__x = float(x) self.__ ...
随机推荐
- 震惊!C++程序真的从main开始吗?99%的程序员都答错了
嘿,朋友们好啊!我是小康.今天咱们来聊一个看似简单,但实际上99%的C++程序员都答错的问题:C++程序真的是从main函数开始执行的吗? 如果你毫不犹豫地回答"是",那恭喜你,你 ...
- C系统级编程-复习
数组对象类型 Array of Type,它是多个相同对象类型的一维派生类型,包含两要素:元素个数,元素的对象类型 所谓多维数组,不过是元素的迭代衍生,本质还是一维的 声明 对象标识的名称 对象类型 ...
- 如何调用CMD实现多个同类文件合并的研究 · 二进制 · 依次 · 文本图像视频音频
引言 视频网站内,使用视频下载嗅探器下载了视频,打开资源管理器一看,是几千个.ts文件,见下图: 通过播放部分视频,发现其实内容是完整的,只是自动切割了多份,倘若无缝拼接为一个完整视频单元,就可以 ...
- markdown设置目录、锚点
目录 在编辑时正确使用标题,在段首输入[toc]即可 锚点 创建到命名锚记的链接的过程分为两步: 首先是建立一个跳转的连接: [说明文字](#jump) 然后标记要跳转到什么位置,注意id要与之前(# ...
- 【Spring】事务操作
事务概念 1.什么事务 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败 典型场景:银行转账 lucy 转账 100 元 给 mary lucy 少 100,mar ...
- C#/.NET/.NET Core优秀项目和框架2025年3月简报
前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的详细介绍.功能特点.使用方式以及部分功能 ...
- nodejs环境准备
这是为了针对nodejs使用来进行的环境准备,分出windows和ubuntu两种情况: Windows 环境 安装 Node.js 下载安装包:访问下面nodejs官网: 选择适合 Windows ...
- docker发布简单python服务
进入机器创建一个目录mkdir dockerbuild1.编写简单flask代码vi flaskapp.pyfrom flask import Flaskimport os app = Flask(_ ...
- Mybatis-Plus中的@TableId
简介 在 MyBatis Plus 中,@TableId 注解是用于标记实体类中的主键字段.它可以更方便地处理主键相关的操作,如自动填充主键值或识别主键字段. 用法 public class User ...
- Ubuntu v22配置用户临界值
方法 1:使用 pam_faillock(推荐,Ubuntu 22.04 默认方式) pam_faillock 是较新的 PAM 模块,用于记录失败登录尝试并在达到限制后锁定账户. 修改 /etc/p ...