python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python抽象基类

一文详解python中抽象基类使用指南

作者:落痕的寒假

在Python中,抽象基类是一类特殊的类,它不能被实例化,主要用于作为基类被其他子类继承,本文小编就来和大家深入介绍一下抽象基类的具体使用,有需要的小伙伴可以参考一下

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

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

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

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

1.创建基础抽象基类

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

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

2.抽象方法 (@abstractmethod)

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

作用:

关键点:

3.继承 (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

2.抽象属性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

3.带类方法的抽象基类

当方法不需要访问或修改实例状态(即不依赖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)     

输出:

解包后 - 纸箱: ['衣服', '鞋子']
解包后 - 泡沫: ['玻璃制品', '陶瓷']

4.带有具体方法的抽象基类

该示例呈现了一个兼具抽象方法与具体方法(实例方法)的抽象基类。抽象基类中既包含子类必须实现的抽象方法,也有提供共享功能的具体方法。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

5.非显式继承

这个示例展示了如何在不进行显式继承的情况下,将类注册为抽象基类的虚拟子类。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)

输出:

嘎嘎嘎
摇摇摆摆地走
人类模仿鸭子叫
人类两条腿走路

6.多重继承

以下例子展示了抽象基类在多重继承中的应用。通过多重继承,可以将多个抽象基类组合,创建出能实现多种接口的类。例如,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的规则:

以上述代码为例,RadioRecorder继承自Listenable和Recordable。Listenable排在Recordable前面,这意味着当两个父类有同名方法时,Listenable的方法会被优先调用。因此这里的MRO顺序是:RadioRecorder -> Listenable -> Recordable -> object。这意味着:

可以使用__mro__属性或mro()方法查看类的MRO顺序:

print(RadioRecorder.__mro__)
(<class '__main__.RadioRecorder'>, <class '__main__.Listenable'>, <class '__main__.Recordable'>, <class 'abc.ABC'>, <class 'object'>)

以上就是一文详解python中抽象基类使用指南的详细内容,更多关于python抽象基类的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文