找回密码
 立即注册
首页 业界区 业界 [python] python抽象基类使用总结

[python] python抽象基类使用总结

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

  • 接口一致性:通过定义抽象方法,抽象基类确保所有子类必须实现这些方法,从而保证子类具有一致的接口;
  • 避免不完整实现:若子类未实现抽象基类中的所有抽象方法,该子类仍会被视为抽象基类,无法实例化;
  • 提高代码可维护性:清晰定义的接口使代码结构更清晰,便于团队协作和系统扩展。
Python要创建抽象基类,需继承abc.ABC并使用@abstractmethod装饰器标记必须被重写的方法。在实际应用中,抽象基类广泛用于以下场景:

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

1 使用入门

创建基础抽象基类
以下代码展示了Python中面向对象编程的几个重要概念:

  • 抽象基类 (Abstract Base Class, ABC)

    • Animal(ABC) 是一个抽象基类,继承自ABC(需要从abc模块导入)。
    • 作用:抽象基类用于定义一组方法的接口规范,但不能被直接实例化。它要求子类必须实现这些方法,否则会报错。
    • 关键点:
      抽象基类通过@abstractmethod装饰器标记抽象方法。
      如果子类没有实现所有抽象方法,Python会阻止子类的实例化。

  • 抽象方法 (@abstractmethod)

    • move()和sound()是抽象方法,用@abstractmethod装饰。
    • 作用:

      • 强制子类必须实现这些方法。
      • 定义了一个统一的接口规范(例如所有动物都必须能“移动”和“发声”)。

    • 关键点:

      • 抽象方法只有声明,没有实现(用pass关键字占位)。
      • 如果子类不实现这些方法,尝试实例化时会引发 TypeError。


  • 继承 (Bird和Fish继承Animal)

    • Bird和Fish是Animal的子类。子类必须实现父类中所有的抽象方法。
    • 不同子类对同一方法有不同的实现。

  1. from abc import ABC, abstractmethodclass Animal(ABC):    @abstractmethod    def move(self):        pass    @abstractmethod    def sound(self):        passclass 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 flyingprint(sparrow.sound()) # 输出: Chirp chirpsalmon = Fish("Salmon")print(salmon.move())   # 输出: Salmon is swimmingprint(salmon.sound())  # 输出: Blub blub# animal = Animal()  # 尝试实例化抽象基类会引发TypeError
复制代码
  1. Sparrow is flyingChirp chirpSalmon is swimmingBlub blub
复制代码
抽象属性Abstract property
以下示例将name方法声明为抽象属性,要求所有继承Person的子类必须实现这个属性。使用@property表示这应该是一个属性而不是普通方法。通过@property装饰器,用于将类的方法转换为"属性",使得可以像访问属性一样访问方法,而不需要使用调用语法(即不需要加括号)。注意子类必须同样使用@property装饰器来实现该属性。
使用@property的优势在于能够控制访问权限,定义只读属性,防止属性被意外修改。例如:
  1. emp = Employee("Sarah", "Johnson")emp.name = "Alice"  # 会报错,AttributeError: can't set attribute
复制代码
示例代码如下:
  1. from abc import ABC, abstractmethodclass Person(ABC):    """抽象基类,表示一个人"""        @property    @abstractmethod    def name(self) -> str:        """获取人的姓名"""        pass    @abstractmethod    def speak(self) -> str:        """人说话的抽象方法"""        passclass 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 attributeprint(emp.name)    # 输出: Sarah Johnsonprint(emp.speak()) # 输出: Hello, my name is Sarah Johnson
复制代码
  1. Sarah JohnsonHello, my name is Sarah Johnson
复制代码
带类方法的抽象基类
当方法不需要访问或修改实例状态(即不依赖self属性)时,使用类方法可以避免创建不必要的实例,从而提高效率并简化代码。
  1. 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)     
复制代码
  1. 解包后 - 纸箱: ['衣服', '鞋子']解包后 - 泡沫: ['玻璃制品', '陶瓷']
复制代码
带有具体方法的抽象基类
该示例呈现了一个兼具抽象方法与具体方法(实例方法)的抽象基类。抽象基类中既包含子类必须实现的抽象方法,也有提供共享功能的具体方法。operate具体方法界定了 “启动→运行→停止” 的通用操作流程,而具体实现则由子类负责。此模式让抽象基类能够把控算法结构,同时将细节实现延迟至子类。这不仅提升了代码的可维护性,还便于在不改动现有代码结构的前提下添加摩托车、飞机等新的交通工具。
  1. from abc import ABC, abstractmethodclass 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()
复制代码
  1. Starting car engineVehicle is in operation...Turning off car engineStarting to pedal bicycleVehicle is in operation...Applying bicycle brakes
复制代码
非显式继承
这个示例展示了如何在不进行显式继承的情况下,将类注册为抽象基类的虚拟子类。register方法允许声明某个类实现了抽象基类,却无需直接继承该基类。
  1. from abc import ABC, abstractmethodclass Vehicle(ABC):    @abstractmethod    def move(self):        passclass 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())
复制代码
  1. TrueTrueDriving 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方法,就可以正常调用。
  1. 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)
复制代码
  1. 嘎嘎嘎摇摇摆摆地走人类模仿鸭子叫人类两条腿走路
复制代码
多重继承
以下例子展示了抽象基类在多重继承中的应用。通过多重继承,可以将多个抽象基类组合,创建出能实现多种接口的类。例如,RadioRecorder类同时继承了Listenable和Recordable两个抽象基类,并实现了它们的所有抽象方法。这种方式既满足了严格的实现要求,又能灵活地定义接口。
  1. 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"))   
复制代码
  1. Listening to FM 98.6Recording 'Music' on FM 98.6
复制代码
如果两个抽象基类有相同的方法名,会导致方法冲突。 Python中,当多重继承的父类存在同名方法时,调用顺序由方法解析顺序。例如以下代码中抽象基类都存在change方法,在子类change方法内部可以根据参数类型分别处理不同的逻辑来避免冲突:
  1. 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  Listenable -> Recordable -> object。这意味着:</p>
  2. [list]
  3. [*]当调用radio.change()时,Python 会先在RadioRecorder中查找change方法。
  4. [*]如果没找到,会在Listenable中查找。
  5. [*]如果还没找到,会在Recordable中查找。
  6. [*]最后查找object类(所有类的基类)。
  7. [/list]可以使用__mro__属性或mro()方法查看类的MRO顺序:
  8. [code]print(RadioRecorder.__mro__)
复制代码
  1. (, , , , )
复制代码
2 参考


  • Python Abstract Classes
  • Python MRO方法解析顺序详解

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册