纸上得来终觉浅,绝知此事要躬行。

如果我们之前接触过Java的抽象类,那么Python的抽象基类就非常好理解了。但如果并不是很了解的话,那么还是有需要一起先看看Java的抽象类,这是非常有必要的。
- Java 抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,如成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
也正是因为这个原因,通常在设计阶段就要决定要不要设计这个抽象类。在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)
上面是ZCA在Python3中的用法,而在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
4
NotImplementedError:
In [3]: p = PluginA()
In [4]: p.load()
Loading
- abc
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):
pass
@name.setter
@abc.abstractmethod
def name(self, value):
pass
@classmethod
@abc.abstractmethod
def method1(cls):
pass
@staticmethod
@abc.abstractmethod
def method2():
pass
- 抽象基类实例化
我们可以在之前的示例中看到,抽象基类是没有办法直接实例化使用的,不然就会抛出如下错误。如果我们非常实例化呢?这里提供了两种实例化的方式。
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,并且在里面实现抽象基类中定义的但没有真正实现的所有属性和方法,如这里的load和save。不然的话,没办法正常使用的。
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
- 延伸阅读
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)