在Python中,抽象基类是一类特殊的类,它不能被实例化,主要用于作为基类被其他子类继承。抽象基类的核心作用是为一组相关的子类提供统一的蓝图或接口规范,明确规定子类必须实现的方法,从而增强代码的规范性和可维护性。Python通过abc(Abstract Base Classes)模块提供了对抽象基类的支持,允许开发者创建和使用抽象基类。

抽象基类的主要特点和用途包括:

  1. 接口一致性:通过定义抽象方法,抽象基类确保所有子类必须实现这些方法,从而保证子类具有一致的接口;
  2. 避免不完整实现:若子类未实现抽象基类中的所有抽象方法,该子类仍会被视为抽象基类,无法实例化;
  3. 提高代码可维护性:清晰定义的接口使代码结构更清晰,便于团队协作和系统扩展。

Python要创建抽象基类,需继承abc.ABC并使用@abstractmethod装饰器标记必须被重写的方法。在实际应用中,抽象基类广泛用于以下场景:

  1. 框架设计:定义接口规范,强制子类实现特定方法,确保框架扩展的一致性;
  2. 插件系统:规定插件必须实现的通用接口,方便系统动态加载第三方模块;
  3. 团队协作:明确模块间的交互契约,避免开发人员遗漏关键方法的实现;
  4. 代码复用:通过抽象基类封装通用逻辑,子类只需实现差异化部分;
  5. 类型检查:结合isinstance()进行运行时类型验证,确保对象符合预期接口;
  6. 复杂系统架构:构建多层次的类继承体系,清晰划分各层级的职责边界。

通过合理使用抽象基类,开发者可以创建更健壮、更具扩展性的代码架构,同时减少因接口不一致导致的错误。

1 使用入门

创建基础抽象基类

以下代码展示了Python中面向对象编程的几个重要概念:

  1. 抽象基类 (Abstract Base Class, ABC)

    • Animal(ABC) 是一个抽象基类,继承自ABC(需要从abc模块导入)。
    • 作用:抽象基类用于定义一组方法的接口规范,但不能被直接实例化。它要求子类必须实现这些方法,否则会报错。
    • 关键点:

      抽象基类通过@abstractmethod装饰器标记抽象方法。

      如果子类没有实现所有抽象方法,Python会阻止子类的实例化。
  2. 抽象方法 (@abstractmethod)
    • move()和sound()是抽象方法,用@abstractmethod装饰。
    • 作用:
      • 强制子类必须实现这些方法。
      • 定义了一个统一的接口规范(例如所有动物都必须能“移动”和“发声”)。
    • 关键点:
      • 抽象方法只有声明,没有实现(用pass关键字占位)。
      • 如果子类不实现这些方法,尝试实例化时会引发 TypeError
  3. 继承 (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,只要该对象拥有quackwalk方法,就可以正常调用。

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类同时继承了ListenableRecordable两个抽象基类,并实现了它们的所有抽象方法。这种方式既满足了严格的实现要求,又能灵活地定义接口。

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抽象基类使用总结的更多相关文章

  1. python之抽象基类

    抽象基类特点 1.不能够实例化 2.在这个基础的类中设定一些抽象的方法,所有继承这个抽象基类的类必须覆盖这个抽象基类里面的方法 思考 既然python中有鸭子类型,为什么还要使用抽象基类? 一是我们在 ...

  2. Python抽象基类之声明协议

    抽象基类之--声明协议 上回讲了Python中抽象基类的大概,相信大家对abcmeta以及什么是抽象基类已经有所了解.传送门 现在我们来讲讲抽象基类的另一个常用用法--声明协议 所谓声明协议,有点像J ...

  3. PythonI/O进阶学习笔记_3.2面向对象编程_python的继承(多继承/super/MRO/抽象基类/mixin模式)

    前言: 本篇相关内容分为3篇多态.继承.封装,这篇为第二篇 继承. 本篇内容围绕 python基础教程这段: 在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法.使 ...

  4. Fluent_Python_Part4面向对象,11-iface-abc,协议(接口),抽象基类

    第四部分第11章,接口:从协议到抽象基类(重点讲抽象基类) 接口就是实现特定角色的方法集合. 严格来说,协议是非正式的接口(只由文档约束),正式接口会施加限制(抽象基类对接口一致性的强制). 在Pyt ...

  5. python面对对象编程---------6:抽象基类

    抽象基本类的几大特点: 1:要定义但是并不完整的实现所有方法 2:基本的意思是作为父类 3:父类需要明确表示出那些方法的特征,这样在写子类时更加简单明白 用抽象基本类的地方: 1:用作父类 2:用作检 ...

  6. 流畅python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  7. Python 接口:从协议到抽象基类

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 抽象基类的常见用途:实现接口时作为超类使用.然后,说明抽象基类如何检查 ...

  8. guxh的python笔记七:抽象基类

    1,鸭子类型和白鹅类型 1.1,白鹅类型 白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__ ...

  9. Python中的对象行为与特殊方法(二)类型检查与抽象基类

    类型检查 创建类的实例时,该实例的类型为类本身: class Foo(object): pass f = Foo() 要测试实例是否属于某个类,可以使用type()内置函数: >>> ...

  10. 【Python】【元编程】【从协议到抽象基类】

    """class Vector2d: typecode = 'd' def __init__(self,x,y): self.__x = float(x) self.__ ...

随机推荐

  1. python 打开yaml文件提示Empty test suite.

    网上方案: 我自己: 将test改为其他名称开头即可 读取成功

  2. linux中如何判断一个rpm是手动安装还是通过yum安装的

    现状 对于一个不熟悉的服务器或者是虽然是自己的服务器,但历史比较久远,对于上面安装了的一些软件包,我们记忆都慢慢模糊了. 我今天遇到一个情况,在安装一个工具x2openEuler时,安装失败,提示依赖 ...

  3. go grpc的入门使用

    简介 什么是grpc grpc是一个由google推出的.高性能.开源.通用的rpc框架.它是基于HTTP2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言. ...

  4. 办公自动化-批量更新tar包内文件

    最近工作有点忙,学习的时间也少了,为了提高工作效率,有时候我们需要自己写一些提高办公处理效率给的工具或者脚本或者程序. 比如,我目前遇到的一个事项,需要更新很多个tar包文件,把tar包内的某个文件替 ...

  5. Oracle 内存(SGA,PGA)详细介绍

    一.名词解释(1)SGA:SystemGlobal Area是OracleInstance的基本组成部分,在实例启动时分配;系统全局域SGA主要由三部分构成:共享池.数据缓冲区.日志缓冲区.(2)共享 ...

  6. delphi Wmi 获取操作系统信息

    uses ActiveX, ComObj; function GetWMIProperty(WMIProperty: string): string; var Wmi, Objs, Obj: OleV ...

  7. 自制一个超级简单的 php 发邮件的轮子 simpleMailTool.php

    simpleMailTool 程序链接 https://github.com/kohunglee/simpleMailTool/ 一个简单的 php 发邮件的轮子,跟其他著名大轮子相比(如 PHPMa ...

  8. 请求的资源不支持 http 方法“GET”。

    错误重现 js ajax调用一个ASP.NET MVC写的api时,一直出现错误信息请求的资源不支持 http 方法"GET". 错误原因 ASP.NET MVC中的MVC(Web ...

  9. 《机器人SLAM导航核心技术与实战》第1季:第9章_视觉SLAM系统

    <机器人SLAM导航核心技术与实战>第1季:第9章_视觉SLAM系统 视频讲解 [第1季]9.第9章_视觉SLAM系统-视频讲解 [第1季]9.1.第9章_视觉SLAM系统_ORB-SLA ...

  10. Python 类型检查与类型注解:mypy 与 typing 深度解析

    Python 类型检查与类型注解:mypy 与 typing 深度解析 在 Python 动态类型语言中,mypy 和 typing 是两个提升代码健壮性的核心工具.它们通过静态类型检查与类型注解,帮 ...