ABC抽象类笔记

  • python抽象类的介绍

python的abc模块提供了对抽象类的支持,通过继承ABC类,可以实现定义一个抽象父类,这个父类可以提前定义好一些抽象接口,可以通过继承抽象父类并实现这些接口方法来定义不同的类

比如我们定义一个图形类

from abc import ABC, abstractmethod

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

这个类就是一个抽象图形类,其中draw方法就是抽象方法,我们不能直接实例化这个类,如果实例化,则会抛出TypeError错误,如:

if __name__ == '__main__':
    s = Shape()
    s.draw()
Traceback (most recent call last):
  File "F:\script\test.py", line 46, in <module>
    c = Shape()
TypeError: Can't instantiate abstract class Shape with abstract methods draw

在使用的时候,我们要给他创建一个可以实例化的子类,这个子类需要继承父类,并实现抽象方法

class Circle(Shape):
    def draw(self):
        print('Circle')


if __name__ == '__main__':
    c = Circle()
    c.draw()

打印结果如下:

Circle

Process finished with exit code 0

如果子类在继承抽象父类之后,不重写他的抽象方法,则依然会报TypeError错误,如改写一下子类的draw方法名:

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

class Circle(Shape):
    def no_draw(self):
        print('Circle')

if __name__ == '__main__':
    c = Circle()
    c.draw()

执行后会报如下错误(跟之前的报错是一样的):

Traceback (most recent call last):
  File "F:\script\test.py", line 46, in <module>
    c = Circle()
TypeError: Can't instantiate abstract class Circle with abstract methods draw

 

  • 抽象类使用场景

希望子类既有相同的缺省方法,又可以拥有自己的实例变量类型,又要约束子类具有相同的行为的时候,可以使用抽象类

比如我们定义一个计算材料消耗的方法,这个方法是根据传入的图形来计算封边用的丝线长度和图形用的布料面积

# 写一个根据传入的图形计算材料消耗的方法
def cost(shape):
    print(f'这个图形需消耗{shape.perimeter()}长度的丝线和{shape.area()}面积的面料')

然后我们定义一个图形抽象基类

class Shape(ABC):
    def print_self(self):
        print('这一个{}类型的实例'.format(self.__class__.__name__))

    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

    @abstractmethod
    def area(self):
        pass

然后定义一个圆形图形类

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def draw(self):
        print('draw a circle of radius:%d'% self.radius)

    # 求圆形的周长
    def perimeter(self):
        print(f'这个图形的周长是{2 * 3.14 * self.radius}')
        return 2 * 3.14 * self.radius

    # 求圆的面积
    def area(self):
        print(f'这个图形的面积是{3.14 * self.radius * self.radius}')
        return 3.14 * self.radius * self.radius

再定义一个长方形图形类

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def draw(self):
        print('draw a rectangle of wight:%d, high:%d'% (self.w, self.h))

    # 求长方形的周长
    def perimeter(self):
        print(f'这个图形的周长是{2 * (self.h + self.w)}')
        return 2 * (self.h + self.w)

    # 求长方形的面积
    def area(self):
        print(f'这个图形的面积是{self.w * self.h}')
        return self.w * self.h

在Shape图形类中,定义了4个方法,分别是print_self方法,draw方法,perimeter方法和area方法,其中print_self方法是类的具体方法,其他三个是类的抽象方法,圆形和长方形的图形类都继承了Shape图形类,但是print_self方法可以直接继承父类的方法,不用重写,但是其他3个抽象方法,需要子类具体实现

接下来我们实例化一个圆形,并调用一下cost方法

if __name__ == '__main__':
    c = Circle(5)
    c.print_self()
    c.draw()
    cost(c)

然后看一下打印结果:

这一个Circle类型的实例
draw a circle of radius:5
这个图形的周长是31.400000000000002
这个图形的面积是78.5
这个图形需消耗31.400000000000002长度的丝线和78.5面积的面料

在代码的执行过程中,首先实例化了一个子类Circle,然后调用了print_self方法,这个方法并不是抽象方法,所以子类无需重写,会直接继承父类的方法,然后打印了“这一个Circle类型的实例”,接下来是draw方法,这个方法是Circle类具体实现的方法,所以会打印这个类特有的内容

接下来我们将这个实例化的圆形传入了cost方法,在cost方法中,调用了这个类求周长和面积的方法,所以会打印出这个图形的周长和面积,并计算出材料消耗

然后我们实例化一个长方形再执行一遍这个流程看看结果

if __name__ == '__main__':
    r = Rectangle(3, 5)
    r.print_self()
    r.draw()
    cost(r)

打印结果:

这一个Rectangle类型的实例
draw a rectangle of wight:3, high:5
这个图形的周长是16
这个图形的面积是15
这个图形需消耗16长度的丝线和15面积的面料

对比上面圆形的执行结果,执行过程没有做过任何改动,但是输出的结果却是各自类型执行的结果,所以抽象类可以在尽可能不改变业务逻辑的前提下,实现对多个同类对象的处理

以上只是以图形做了一个简单的例子,实际上抽象类的应用场景远不止如此,比如在游戏中,我们可以定义界面抽象类,约束不同的界面有相同的行为(打开,关闭,跳转等),再比如道具抽象类、怪物抽象类等等

  • 总结

抽象类是面向对象编程中的一个重要概念,通过抽象类,我们可以规范化程序的设计,提高代码的复用性,让代码变的更加的灵活且便于维护,所以在实际开发的时候,根据业务功能,对于相同的类型尽可能的使用抽象类。但需要注意的是定义抽象类时,一定要明确约束需要子类各自具体实现的方法,以保证抽象类的规范和可维护性。

 

posted @ 2023-07-17 11:30  碧晓寒枫  阅读(37)  评论(0)    收藏  举报