[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.__ ...
随机推荐
- wxformbuilder 如何生成python 代码
?问题 正常通过F8->F6 ,我执行这两步操作后如下图,以.fbp格式显示,没生成文件 解决方案 object properties 下勾选python 效果图:
- 【记录】C-文件输入输出
写在开头 摸鱼摸得昏天黑地,是该炸一炸鱼的本愿了~ 以前总觉得笔记没什么重要的,但有时事到临头,又十分渴求简明的提纲来唤起记忆/提供重学的门路.于是就有了以下的产物,也希望能抑制一下无效摸鱼的堕落 ...
- [I.2]个人作业:软件案例分析
项目 内容 这个作业属于哪个课程 2025春季软件工程(罗杰.任健) 这个作业的要求在哪里 [I.2]个人作业:软件案例分析 我在这个课程的目标是 在PSP中精进个人代码技术,在TSP中提高团队合作凝 ...
- kubernetes failed to create kubelet: misconfiguration: kubelet cgroup driver: "cgroupfs" is different from docker cgroup driver: "systemd"
错误原因 kubernetes 的文件驱动与 docker 不一致,导致镜像无法启动. docker info 可以看到驱动方式 Cgroup Driver: systemd. 解决方案 统一资源管理 ...
- laradock 更改 mysql 版本
# 修改 .env 文件 MYSQL_VERSION=5.7 # 默认为 latest #停止mysql容器 docker-compose stop mysql # 删除旧数据库数据 rm -rf ~ ...
- docx4j转换HTML并生成word文档实践
一.背景 在项目开发中,有一个需求需要将富文本编辑器中的内容转换为word文档.在网上看了很多开源第三方工具包的对比,最终选择了docx4j,主要原因有一下几点: 可以将html转换为word 对wo ...
- LaTeX配置说明
LaTex_intro 1.VSCode 安装 微软编辑器:VSCode 配置 安装 LaTeX Workshop 插件 settings.json 路径为:C:\Users\Administrato ...
- javascript 数值交换技巧
需求 现在存在var a = 1,b = 2;,要求使用javascript实现a & b数值的交换,让a = 2,b = 1. 中间变量(常规实现方式) var a = 1, b = 2; ...
- restful 服务器一个问题,看ChatGPT的威力 (续)
资料很多,但是真正能经得住7X24运行的还真不容易.说穿了就是你的程序不够健壮. 玩数据处理的,也就是:数据库连接 → 查询 → 拉数据 → 生成结果 → 释放连接 → 返回数据 .可是看下面: FD ...
- 记一次 .NET某云HIS系统 CPU爆高分析
一:背景 1. 讲故事 年前有位朋友找到我,说他们的系统会偶发性的CPU爆高,有时候是爆高几十秒,有时候高达一分多钟,自己有一点分析基础,但还是没找到原因,让我帮忙看下怎么回事? 二:CPU爆高分析 ...