在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,如成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。

也正是因为这个原因,通常在设计阶段就要决定要不要设计这个抽象类。在 Java 中抽象类表示的是一种继承的关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。在 Python 的早期,也有接口、基类这样的模式。

  • Zope 组件架构
  • 开发一个大型的软件,通常是非常复杂的,总体上都有一个结构和方案。下面说的这个 Zope 组件架构 ( ZCA ),即 Zope Component Architecture ZCA 非常的知名,它是一个支持基于组件设计和编程的 Python 框架,非常适合大型的 Python 软件系统。

    我们因为它的名称而感到迷惑,因为 ZCA 并非只是为了 Zope 这个网站应用服务器所专用的,而是能够用来开发任何 Python 的应用程序。但是,它的写法类似于 Java 且写起来十分的繁琐,是合适 Python 敏捷开发的哲学思想。而且, ZCA 多为传统的应用程序常常采用的架构,且不做推广,导致现在成为一个比较小众的架构模型。

    下面就是一个 ZCA 架构的示例,在 IFoo 类中定义了一个 Interface 和两个 Attribute 的变量,而在使用的时候需要在 Foo 类中去实现对应变量,方法也是类似的。

    from zope.interface import Interface, Attribute, implements, implementer
    class IFoo(Interface):
        x = Attribute("The X attribute")
        y = Attribute("The Y attribute")
    @implementer(IFoo)
    class Foo:
        x = 1
        def__init__(self):
            self.y = 2
    
    In [1]: foo = Foo()
    In [2]: foo.x, foo.y
    Out[2]: (1, 2)
    

    上面是ZCAPython3中的用法,而在Python2中需要使用如下的方式,程序才能够正常运行。

    class Foo:
        implements(IFoo)
        x = 1
        def __init__(self):
            self.y = 2
    

    通过这个示例,可以感受到抽象基类和普通的类的不同之处在于,抽象类中只能有抽象的方法并没有实现真正的功能,所以这个类不能直接实例化只能执行继承,而且子类中必须实现对应的属性和方法才能正常运行。

  • 抽象类的使用方法
  • 那有没有更简单的方案呢?其实是有的,第一种方案就是使用NotImplementedError这样一个错误,第二种方案就是使用标准库中的abc模块。

  • NotImplementedError
  • 在基类PluginBase中的load里面抛出了NotImplementedError这样一个错误,而在其继承子类PluginA里面真正的实现。这样当我们实例化基类的时候,虽然能够实现但是不能使用,而实例化子类就是可以的。

    另外,在抛出错误的时候,最好输出一些有用的信息,方便排错和发现问题。

    class PluginBase:
        def load(self):
            raise NotImplementedError
    class PluginA(PluginBase):
        def load(self):
            print('Loading')
    
    In [1]: p = PluginBase()
    In [2]: p.load()
    ---------------------------------------------------------------------------
    NotImplementedError                       Traceback (most recent call last)
    <ipython-input-12-ca63a635fc54> in <module>()
    ----> 1 p.load()
    <ipython-input-9-43a49ee371ca> in load(self)
          1 class PluginBase:
          2     def load(self):
    ----> 3         raise NotImplementedError
    NotImplementedError:
    In [3]: p = PluginA()
    In [4]: p.load()
    Loading
    

    abc模块是Abstract Base Classes的缩写,意为抽象基类。首先,需要声明一个抽象的方法,需要继承的元类来自abc.ABCMeta。然后给基类中还没有实现的方法,添加abc.abstractmethod这个装饰器。

    import abc
    class PluginBase(metaclass=abc.ABCMeta):
        @abc.abstractmethod
        def load(self, input):
            ...
        @abc.abstractmethod
        def save(self, output, data):
            ...
    

    Python3中使用抽象基类,对静态方法、类方法、实例方法和property等等,统一使用abc.abstractmethod这个装饰器就可以了,其他的就不用再使用了。

    class A(metaclass=abc.ABCMeta):
        @property
        @abc.abstractmethod
        def name(self):
        @name.setter
        @abc.abstractmethod
        def name(self, value):
        @classmethod
        @abc.abstractmethod
        def method1(cls):
        @staticmethod
        @abc.abstractmethod
        def method2():
    
  • 抽象基类实例化
  • 我们可以在之前的示例中看到,抽象基类是没有办法直接实例化使用的,不然就会抛出如下错误。如果我们非常实例化呢?这里提供了两种实例化的方式。

    In [1]: from plugin import PluginBase
    In [2]: p = PluginBase()
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-2-a39363c951ce> in <module>()
    ----> 1 p = PluginBase()
    TypeError: Can't instantiate abstract class PluginBase with abstract methods load, save
    

    使用之前我们讲到的子类的形式来实例化,子类Plugin继承抽象基类PluginBase,并且在里面实现抽象基类中定义的但没有真正实现的所有属性和方法,如这里的loadsave。不然的话,没办法正常使用的。

    class Plugin(PluginBase):
        def load(self, input):
            print('Loading')
        def save(self, output, data):
            print('Saving')
    
    In [1]: from base import Plugin
    In [2]: p = Plugin()
    In [3]: p.load('aaaa')
    Loading
    
  • 注册抽象类
  • 这里我们使用register注册的形式来使用抽象类,而没有继承。注册之后,发现PluginA就是PluginBase的子类,PluginA()也是PluginBase的实例,体现了一个继承的效果。

    from plugin import PluginBase
    class PluginA:
        def load(self, input):
            return input
        def save(self, output, data):
            return output.write(data)
    
    In [1]: PluginBase.register(PluginA)
    Out[1]: __main__.PluginA
    In [2]: issubclass(PluginA, PluginBase)
    Out[2]: True
    In [3]: isinstance(PluginA(), PluginBase)
    Out[3]: True
    

    这种写法,相对于继承来说,更加的灵活。它可以动态的让子类去继承,当然也可以取消继承。这里就是用了一种极端的方式,把子类给注销掉,之后判断PluginA就在不再是PluginBase的子类了。

    In [4]: PluginBase._abc_registry.clear()
    In [5]: PluginBase._abc_cache.clear()
    In [6]: issubclass(PluginA, PluginBase)
    Out[6]: False
    
  • 模块 abc 的官网文档
  • 模块 abc 的实例用法
  • PEP 文档介绍 abc 模块
  • 抽象基类的实际用法
  • class Iterator(Iterable):
        __slots__ = ()
        @abstractmethod
        def __next__(self):
            'Return the next item from the iterator. When exhausted, raise StopIteration'
            raise StopIteration
        def __iter__(self):
            return self
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Iterator:
                return _check_methods(C, '__iter__', '__next__')
            return NotImplemented
    Iterator.register(bytes_iterator)
    Iterator.register(bytearray_iterator)
    Iterator.register(dict_keyiterator)
    Iterator.register(dict_valueiterator)
    Iterator.register(dict_itemiterator)
    Iterator.register(list_iterator)
    Iterator.register(list_reverseiterator)
    Iterator.register(range_iterator)
    Iterator.register(longrange_iterator)
    Iterator.register(set_iterator)
    Iterator.register(str_iterator)
    Iterator.register(tuple_iterator)
    Iterator.register(zip_iterator)