Python实现抽象基类的3三种方法

Python的抽象基类类似于Java、C++等面向对象语言中的接口的概念。抽象基类提供了一种要求子类实现指定协议的方式,如果一个抽象基类要求实现指定的方法,而子类没有实现的话,当试图创建子类或者执行子类代码时会抛出异常。这里简单介绍一下Python实现抽象基类的三种方法。


方法一:使用NotImplementedError

见下面的测试代码,只有子类实现了run方法才能运行run。

>>> class Task():
    def __init__(self, x, y):
        self.x = x
        self.y = y

>>> class Task():
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def run(self):
        raise NotImplementedError('Please define "a run method"')

>>> t = Task(1, 2)
>>> t.run()
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    t.run()
  File "<pyshell#10>", line 6, in run
    raise NotImplementedError('Please define "a run method"')
NotImplementedError: Please define "a run method"
>>>
>>> class SubTask(Task):
    def __init__(self, x, y):
        super().__init__(x, y)

    def run(self):
        print('Task(x=%s, y=%s)' % (self.x, self.y))

>>> st = SubTask(1, 3)
>>> st.run()
Task(x=1, y=3)
>>> 


方法二:使用元类

class TaskMeta(type):
    def __new__(cls, name, bases, attrs):

        new_class = super(TaskMeta, cls).__new__(cls, name, bases, attrs)

        if attrs.pop('abstract', False):
            return new_class

        if not hasattr(new_class, 'run') or not callable(new_class.run):
            raise TypeError('Please define "a run method"')

        return new_class


class Task(metaclass=TaskMeta):
    abstract = True

    def __init__(self, x, y):
        self.x = x
        self.y = y


class SubTask(Task):
    def __init__(self, x, y):
        super().__init__(x, y)

    def run(self):
        print('Task(x=%s, y=%s)' % (self.x, self.y))

测试代码一:

>>> t = Task(1, 3)
>>> t.run()
Traceback (most recent call last):
  File "E:/Code/python3/loggingTest/task.py", line 32, in <module>
    t.run()
AttributeError: 'Task' object has no attribute 'run'
>>> st = SubTask(1, 3)
>>> st.run()
Task(x=1, y=3)


这个示例类似于方法一,但有一些细微的区别。第一个区别就是Task类本身仍然能被实例化,但是不能运行run方法,否则会抛出AttributeError错误。更为重要的区别在于子类。当子类被创建时元类会运行__new__方法,解释器讲不再允许创建没有run方法的子类。

>>> class SubTask(Task):
...    pass
...
Traceback (most recent call last):
  File "E:/Code/python3/loggingTest/task.py", line 31, in <module>
    class SubTask(Task):
  File "E:/Code/python3/loggingTest/task.py", line 10, in __new__
    raise TypeError('Please define "a run method"')
TypeError: Please define "a run method"


方法三:使用@abstractmethod

  abc模块提供了一个使用某个抽象基类声明协议的机制,并且子类一定要提供了一个符合该协议的实现。

import abc


class Task(metaclass = abc.ABCMeta):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @abc.abstractmethod
    def run(self):
        pass


class SubTask(Task):
    def __init(self, x, y):
        super().__init__(x, y)

    def run(self):
        print('Task(x=%s, y=%s)' % (self.x, self.y))

class OtherSubTask(Task):
    def __init(self, x, y):
        super().__init__(x, y)

和方法一、方法二的示例类似,但略有不同。第一,Task类本身不能被实例化。

>>> t = Task(1, 3)
Traceback (most recent call last):
  File "E:/Code/python3/loggingTest/test.py", line 23, in <module>
    t = Task(1, 3)
TypeError: Can't instantiate abstract class Task with abstract methods run


这与方法一不同,方法一允许基类Task被实例化。

对于不能正确重写run方法的子类,在错误的情况下它与之前的两个方法的差别也是不同的。方法一中,使用NotImplementedError,最终在run方法被调用时引发NotImplementedError错误。在方法二中,使用了自定义的TaskMeta元类, 当这个抽象类被创建时引发TypeError错误。

当没有实现run方法的子类实例化时会报错,给出的错误信息与实例化Task类时给出的一样,逻辑上完全符合预期。

>>> ot = OtherSubTask(1, 3)
Traceback (most recent call last):
  File "E:/Code/python3/loggingTest/test.py", line 27, in <module>
    ot = OtherSubTask(1, 3)
TypeError: Can't instantiate abstract class OtherSubTask with abstract methods run
但是,当你定义了一个重新了run方法的子类时,那么子类就能够被实例化,就能正常工作。

>>> st = SubTask(1, 3)
>>> st.run()
Task(x=1, y=3)



  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个关于面向对象编程的问题。以下是代码实现: ```python from abc import ABC, abstractmethod import math class Shape(ABC): @abstractmethod def area(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return math.pi * (self.radius ** 2) class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height class Triangle(Shape): def __init__(self, base, height): self.base = base self.height = height def area(self): return 0.5 * self.base * self.height def printArea(shape): print("面积为:", shape.area()) # 测试代码 circle = Circle(5) rectangle = Rectangle(4, 6) triangle = Triangle(3, 8) printArea(circle) printArea(rectangle) printArea(triangle) shapes = [circle, rectangle, triangle] for shape in shapes: printArea(shape) ``` 这段代码中,我们定义了一个抽象基类`Shape`,其中包含了一个抽象方法`area()`。由`Shape`类派生出`Circle`、`Rectangle`和`Triangle`三个具体的形状类,并且实现了它们的`area()`方法。我们还定义了一个`printArea()`函数,用于输出一个形状的面积。最后,我们进行了一些简单的测试,分别输出了三种形状的面积,并且使用`Shape`类的指针输出了它们的面积。 注意,在定义抽象基类时,我们使用了`abc`模块中的`ABC`类和`abstractmethod`装饰器。这是Python实现抽象类的方法。一个抽象类不能被实例化,只能被用作其他类的基类。抽象类的子类必须实现父类中定义的抽象方法,否则它也会被视为抽象类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值