Python抽象基类之声明协议

抽象基类之--声明协议

上回讲了Python中抽象基类的大概,相信大家对abcmeta以及什么是抽象基类已经有所了解。传送门

现在我们来讲讲抽象基类的另一个常用用法--声明协议

所谓声明协议,有点像Java中的接口这个概念。就是子类必须实现父类要求的方法。

1.不使用抽象基类来实现

1. 提出异常(规范起见请使用 NotImplementedError)

class Test(object):
    def __init__(self):
        self.salary = []

    def work(self):
        money = self._job()
        self.salary.append(money)
        return money

    def _job(self):
        raise NotImplementedError("Test子类必须实现 '_job' 方法")

如上所示的类,如果子类没有自己重写_job方法而执行work方法就会产生NotImplementedError异常,这样通过异常的方法就可以强迫子类必须实现父类规定的函数,但是这有个缺点:如果子类不调用work方法,那么子类就算没有实现_job方法也不会报错。

2. 使用元类

通过元类也可以实现声明协议的功能

class TestMeta(type):
    def __new__(cls, name, bases, attrs):
        # 首先判断该类是不是基类(通过手动定义 abstract 属性来判定)
        if attrs.pop("abstract", False):
            return super(TestMeta, cls).__new__(cls, name, bases, attrs)

        # 否则就是子类,就要判断子类有没有一个叫 _job 的方法
        _job = attrs.get("_job", None)

        if _job and callable(_job):
            return super(TestMeta, cls).__new__(cls, name, bases, attrs)

        # 子类为定义 _job 方法
        raise TypeError("Test子类必须要定义 _job 方法")


class Test(metaclass=TestMeta):
    abstract = True

    def __init__(self):
        self.salary = []

    def work(self):
        money = self._job()
        self.salary.append(money)
        return money


# 这个类会报错
# class SubTestA(Test):
#     pass

class SubTestB(Test):
    def _job(self):
        return 3000

上面的例子中,我们通过元类来检查创建的子类是否包含有_job方法。相比起方法一,该方法必须要求子类定义_job类无论有没有调用过_job方法。但是与Java中的接口相比又少了一点什么?Java中的接口是不可以直接实例化对象的,但是在这个例子中我们可以直接创建Test的对象。这时就该抽象基类上场了。

2.抽象基类实现声明协议

我们可以使用abc模块中的一个装饰器--abstractmethod来装饰一个类的方法使其成为一个抽象方法,拥有抽象方法的类就不可以被实例化(像不像C++中的虚函数)举个例子。

import abc


class Test(metaclass=abc.ABCMeta):
    def __init__(self):
        self.salary = []

    def work(self):
        money = self._job()
        self.salary.append(money)
        return money

    @abc.abstractmethod
    def _job(self):
        raise NotImplementedError("Test子类必须实现 '_job' 方法")


# t = Test()    # 报错: TypeError: Can't instantiate abstract class Test with abstract methods _job


# 子类可以被创建,但是不能初始化
class SubTestA(Test):
    pass


# sta = SubTestA()    # 报错: TypeError: Can't instantiate abstract class SubTestA with abstract methods _job

# 实现抽象函数的子类可以被实例化
class SubTestB(Test):
    def _job(self):
        return 3000

stb = SubTestB()

对比一下上述三种方法的区别:

异常 元类 抽象基类
在调用方法时产生异常 在定义类时产生异常 在实例化对象时产生异常

posted @ 2019-07-27 11:14  KainHuck  阅读(395)  评论(0编辑  收藏  举报