Python——抽象基类ABC的使用


Python定义抽象类需要使用ABC

  • 定义抽象类的关键步骤
    • 抽象基类 :需要继承ABC
    • 抽象方法 :需要使用@abstractmethod装饰器标记的抽象方法
  • 这样做的好处:
    • 防止实例化抽象基类本身不能被实例化(不继承ABC则不算是抽象基类,其子类不做实例化的检查),只能通过子类继承并实现抽象方法后才能使用
    • 可读性 :继承了ABC类的抽象接口更容易阅读

ABC类的说明及使用

  • 在Python中,ABC类(Abstract Base Class,抽象基类)用于 定义抽象基类。抽象基类的主要目的是为其他类提供一个共同的接口或规范,确保子类实现特定的方法或属性
  • 注意:注意是用于定义抽象基类,不是抽象类,ABC是抽象基类的基类

定义抽象类的最佳实践

  • 为了代码的清晰性和规范性,建议继承ABC定义抽象基类。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    from abc import ABC, abstractmethod

    class Shape(ABC):
    @abstractmethod
    def area(self):
    pass

    @abstractmethod
    def perimeter(self):
    pass

    class Rectangle(Shape):
    def __init__(self, width, height):
    self.width = width
    self.height = height

    def area(self):
    return self.width * self.height

    def perimeter(self):
    return 2 * (self.width + self.height)

    # 实例化子类
    rect = Rectangle(5, 10)
    print(rect.area()) # 输出: 50
    print(rect.perimeter()) # 输出: 30

    # 实例化检查:如果子类未实现抽象方法,实例化时会抛出 TypeError
    class Circle(Shape):
    pass

    circle = Circle() # 抛出 TypeError: Can't instantiate abstract class Circle

抽象基类不继承ABC会怎样?

  • 如果不继承ABC,可以通过以下方式实现类似抽象基类的功能:

替代方法:手动抛出NotImplementedError

  • 一种不继承ABC但实现抽象类的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class MyAbstractClass:
    def my_abstract_method(self):
    raise NotImplementedError("Subclasses must implement this method")

    class MyConcreteClass(MyAbstractClass):
    def my_abstract_method(self):
    print("Implemented abstract method")

    # 实例化子类
    obj = MyConcreteClass()
    obj.my_abstract_method() # 输出: Implemented abstract method

    # 如果子类未实现抽象方法,调用时会抛出异常
    class IncompleteClass(MyAbstractClass):
    pass

    obj = IncompleteClass()
    obj.my_abstract_method() # 抛出 NotImplementedError

错误方法示例:使用abc模块但不继承ABC

  • 下面的方法仅使用@abstractmethod,无法实现抽象类,必须要继承ABC才行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from abc import abstractmethod

    # 不继承ABC,增加abstractmethod也没用
    class MyAbstractClass:
    @abstractmethod
    def my_abstract_method(self):
    pass

    # 如果子类未实现抽象方法,实例化时也不会抛出异常
    class IncompleteClass(MyAbstractClass):
    pass

    obj = IncompleteClass() # 可以正常初始化

总体结论

  • 继承 ABC 并使用 @abstractmethod 是定义抽象基类的必须方式
  • 如果选择不继承 ABC,可以通过抛出 NotImplementedError 或手动检查方法实现来模拟抽象基类的行为,但这种方式不够优雅且容易出错

附录:抽象方法实现时的参数规则

  • 核心结论:
    • 抽象方法的子类实现,参数可以不一致 ,Python 语法层面完全允许,不会报错
    • 但是 强烈不建议参数不一致 ,违背抽象基类的设计初衷,会造成严重的代码问题和逻辑混乱,比如多态无法使用

语法层面:子类实现抽象方法,参数完全可以不一样(实测验证)

  • Python 是弱语法约束的语言,对于抽象方法的重写,不强制要求子类方法的形参 和 父类抽象方法的形参完全一致 ,包括 参数个数、参数名称、默认参数 都可以不同,代码能正常运行,不会报语法/运行时错误
  • 示例:形参个数、名称完全不同(合法,无报错)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from abc import ABC, abstractmethod

    # 抽象基类:抽象方法有2个参数
    class Animal(ABC):
    @abstractmethod
    def speak(self, sound, volume):
    pass

    # 子类实现:只有1个参数,参数名也不一样(语法完全允许)
    class Dog(Animal):
    def speak(self, voice):
    print(f"小狗:{voice}")

    # 实例化+调用,完全正常运行
    d = Dog()
    d.speak("汪汪汪~") # 输出:小狗:汪汪汪~

为什么 强烈不建议参数不一致 ?

  • Python 语法允许 不等于 写法合理,参数不一致 会彻底违背我们使用抽象基类的核心目的,主要有3个致命问题:
    • 违背抽象基类的设计初衷
    • 引发多态失效 + 调用报错
    • 造成代码可读性极差 + 协作灾难

附录:父类初始化

  • 如果重写了父类的初始化方法,一定要在子类的 __init__ 函数中 执行 super().__init__() ,以完成父类的初始化
    • __init__ 是 Python 的构造方法,用于初始化实例属性
    • 子类继承父类时,如果子类重写了 __init__ 但不主动调用父类的 __init__ ,父类的构造方法不会被自动执行,进而导致父类中定义的属性 / 初始化逻辑完全失效