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
32from abc import ABC, abstractmethod
class Shape(ABC):
def area(self):
pass
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
18class 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
13from abc import abstractmethod
# 不继承ABC,增加abstractmethod也没用
class MyAbstractClass:
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
16from abc import ABC, abstractmethod
# 抽象基类:抽象方法有2个参数
class Animal(ABC):
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__,父类的构造方法不会被自动执行,进而导致父类中定义的属性 / 初始化逻辑完全失效