Hexo

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

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__ ,父类的构造方法不会被自动执行,进而导致父类中定义的属性 / 初始化逻辑完全失效

Python——多重继承


Python多重继承简单示例

  • Python多重继承简单示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Parent1:
    def method1(self):
    print("This is method 1 from Parent1")

    class Parent2:
    def method2(self):
    print("This is method 2 from Parent2")

    class Child(Parent1, Parent2):
    def child_method(self):
    print("This is a method from Child")

    # 创建Child类的实例
    child = Child()
    # 调用从Parent1继承的方法
    child.method1()
    # 调用从Parent2继承的方法
    child.method2()
    # 调用Child类自身的方法
    child.child_method()

super().__init__()的调用规则:

  • super().__init__()前,需要先构造整个MRO,super().__init__()本质实在调用MRO中该类的下一个

  • 所以:在单继承时会调用当前类的父类,但在调用MRO中的第一个方法父类的初始化,且仅调用第一个

  • 多重继承时,父类的MRO相对顺序会得到保障,但是可能会插入其他类(注:此时父类调用super().__init__()时可能不再直接调用其真实父类的初始化函数,而是MRO中的下一个)

    • 比如没有D时,B的直接父类是A(B的MRO是B-next->A),但是因为在D的MRO中,B的后一个是C(D的MRO是B-next->C),此时如果B没有初始化super().__init__(),则# Initializing C不会打印
  • 代码示例1:

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    class Base1():
    def __init__(self):
    self.name = "Base1"
    print("Initializing Base1")

    def show(self):
    print(f"Name from Base1: {self.name}")

    class Base2():
    def __init__(self):
    self.name = "Base2"
    print("Initializing Base2")

    def show(self):
    print(f"Name from Base2: {self.name}")

    class DerivedUsingInit(Base1, Base2):
    def __init__(self):
    Base1.__init__(self)
    Base2.__init__(self)
    print("Initializing DerivedUsingInit")


    print("\nUsing init for initialization:")
    derived1 = DerivedUsingInit()
    derived1.show()
    print("DerivedUsingInit.__mro__:", DerivedUsingInit.__mro__)

    print("------------------")
    class DerivedUsingSuper(Base1, Base2):
    def __init__(self):
    super().__init__() # 仅调用Base1的__init__函数,不会调用Base2的
    print("Initializing DerivedUsingSuper")

    print("\nUsing super() for initialization:")
    derived2 = DerivedUsingSuper()
    derived2.show()
    print("DerivedUsingSuper.__mro__:", DerivedUsingSuper.__mro__)

    # Initializing Base1
    # Initializing Base2
    # Initializing DerivedUsingInit
    # Name from Base1: Base2
    # DerivedUsingInit.__mro__: (<class '__main__.DerivedUsingInit'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class 'object'>)
    # ------------------
    #
    # Using super() for initialization:
    # Initializing Base1
    # Initializing DerivedUsingSuper
    # Name from Base1: Base1
    # DerivedUsingSuper.__mro__: (<class '__main__.DerivedUsingSuper'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class 'object'>)
  • 代码示例2:

    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
    class A:
    def __init__(self):
    print("Initializing A")

    class B(A):
    def __init__(self):
    super().__init__() # 单继承时调用A的init,多继承时可能调用其他的类的init
    print("Initializing B")

    class C(A):
    def __init__(self):
    super().__init__()
    print("Initializing C")

    class D(B, C):
    def __init__(self):
    super().__init__()
    print("Initializing D")

    d = D()
    print("D.__mro__: ", D.__mro__)
    print("B.__mro__: ", B.__mro__)

    # Initializing A
    # Initializing C
    # Initializing B
    # Initializing D
    # D.__mro__: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    # B.__mro__: (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
  • 代码示例3:

    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
    class A:
    def __init__(self):
    print("Initializing A")

    class B(A):
    def __init__(self):
    # super().__init__()
    print("Initializing B")

    class C(A):
    def __init__(self):
    super().__init__()
    print("Initializing C")

    class D(B, C):
    def __init__(self):
    super().__init__()
    print("Initializing D")

    d = D()
    print("D.__mro__: ", D.__mro__)
    print("B.__mro__: ", B.__mro__)

    # Initializing B
    # Initializing D
    # D.__mro__: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    # B.__mro__: (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

MRO的简单理解

  • Python 2.3 及以后的版本采用 C3 线性化算法来确定 MRO,C3 线性化算法的核心目标是保证 MRO 满足三个重要特性:
    • 单调性:子类必须在父类之前被检查
    • 局部优先性:类定义中父类的顺序会被保留
    • 一致性:如果一个类继承自多个父类,那么 MRO 必须保证所有父类的 MRO 顺序一致
    • 注意:对于相同名称的同一个函数,MRO顺序靠前的生效,靠后的会被靠前的覆盖
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A:
    pass

    class B(A):
    pass

    class C(A):
    pass

    class D(B, C):
    pass

    # 打印 D 类的 MRO
    print(D.mro())

继承顺序要满足MRO规则

  • 错误示例,下面的GrandChild1不满足MRO规则,会报错:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Parent:
    def __init__(self):
    self.value = "I'm from Parent"

    class Child1(Parent):
    def __init__(self):
    self.value = "I'm from Child1"

    class Child2(Parent):
    def __init__(self):
    self.value = "I'm from Child2"

    class GrandChild(Child1, Child2, Parent): # OK
    def __init__(self):
    self.value = "I'm from GrandChild"

    class GrandChild1(Parent, Child1, Child2): # 报错
    def __init__(self):
    self.value = "I'm from GrandChild1"
    # TypeError: Cannot create a consistent method resolution order (MRO) for bases Parent, Child1, Child2

补充:super()函数详细说明

  • super()函数本质是返回了当前类的 MRO 的一个下一个对象,对于单继承模式而言,就是当前类的父类

super()函数的高阶用法

  • super()函数还可以传入参数,示例如下:

    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
    class A:
    def method(self):
    print("Method in A")

    class B(A):
    def method(self):
    print("Method in B")

    class C(A):
    def method(self):
    print("Method in C")

    class D(B, C):
    def method(self):
    super(B, self).method() # 调用C类的method方法
    # super(B, self).method1() # 报错,因为C类没有method1方法
    print("Method in D")

    d = D()
    d.method()
    print(D.__mro__)
    print(B.__mro__)

    # Method in C
    # Method in D
    # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    # (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
    • super(B, self) 是按照方法解析顺序(MRO)来查找 B 类在当前实例 self 的继承关系中的下一个类
    • Python会根据类的MRO来确定方法的调用顺序,MRO是一个列表,它定义了类及其父类的搜索顺序。当使用 super(B, self) 时,它会在 self 所属类的MRO中,从 B 类的下一个位置开始查找方法。例如在多重继承中,通过这种方式可以明确指定从某个类之后的MRO顺序中查找方法,以实现特定的方法调用逻辑
    • 在这个例子中, D 类继承自 B 和 C 类,而 B 和 C 又都继承自 A 类。在 D 类的 method 方法中,使用 super(B, self).method() 明确指定跳过 B 类,调用 C 类中继承自 A 类的 method 方法
  • 注意:在Python的 super() 函数中,默认是调用自身类和对象作为参数的 super 函数,例如:

    1
    2
    3
    4
    class A(B):
    def __init__(self):
    # 等价于调用super().__init__(),
    super(A, self).__init__()
    • 注:Python3中调用super(A, self).__init__() 等价于调用super().__init__(),但在部分Python2旧版本中必须明确指明类和对象
  • 自定义类层次结构中的方法调用

    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
    class Base:
    def operation(self):
    print("Base operation")

    class Derived1(Base):
    def operation(self):
    print("Derived1 operation")

    class Derived2(Base):
    def operation(self):
    print("Derived2 operation")

    class Composite:
    def __init__(self):
    self.derived1 = Derived1()
    self.derived2 = Derived2()
    def operation(self):
    super(Derived1, self.derived1).operation() # 调用Base的operation方法
    super(Derived2, self.derived2).operation() # 调用Base的operation方法
    composite = Composite()
    composite.operation()
    print(Composite.__mro__)
    print(Derived1.__mro__)
    print(Derived2.__mro__)

    # Base operation
    # Base operation
    # (<class '__main__.Composite'>, <class 'object'>)
    # (<class '__main__.Derived1'>, <class '__main__.Base'>, <class 'object'>)
    # (<class '__main__.Derived2'>, <class '__main__.Base'>, <class 'object'>)
    • 在 Composite 类中,通过 super(cls, instance) 的方式,分别调用了 Derived1 和 Derived2 类的父类 Base 中的 operation 方法。这种方式可以在自定义的类层次结构中,灵活地控制方法的调用路径
    • 不建议使用过于复杂的操作,非必要也不做多重继承,通常情况下,使用默认的 super() 函数调用方式就能满足大多数的编程需求,但是很多官方的库中,会明确显式知名其自身类和对象,比如PyTorch的 nn.Linear 类的实现:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Linear(Module):
      """...comments..."""
      # ...
      def __init__(self, in_features: int, out_features: int, bias: bool = True,
      device=None, dtype=None) -> None:
      factory_kwargs = {'device': device, 'dtype': dtype}
      super(Linear, self).__init__() # 这里是显示调用其父类的初始化函数,等价于 super().__init__()
      self.in_features = in_features
      self.out_features = out_features
      self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))
      if bias:
      self.bias = Parameter(torch.empty(out_features, **factory_kwargs))
      else:
      self.register_parameter('bias', None)
      self.reset_parameters()
1…220221222…352
San Ye

San Ye

Stay Hungry. Stay Foolish.

704 posts
53 tags
© 2026 San Ye
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4