4.7 abc模块
创建抽象基类的核心方法定义在abc模块中。此模块中包含的ABCMeta类提供了一些有用的特性。
首先,ABCMeta类保证抽象基类不可以被实例化。但是,一个提供了所有必须实现的子类可以被实例化。这个元类型会在执行new()的时候调用抽象基类中的subclasshook()特殊方法。如果这个方法返回NotImplemented,就会抛出一个异常,指出当前类没有实现所有必需的方法。
其次,它提供了instancecheck()和subclasscheck()的定义。这两个特殊方法实现了isinstance()和issubclass()两个内置的函数。它们用于确保一个对象(或者类)属于正确的抽象基类。同时,为了提高性能,这个类也会包括一个子类的缓存。
abc模块还包括了许多用于创建抽象方法函数的装饰器,子类中必须实现用这些装饰器定义的抽象方法。其中最重要的是@abstractmethod装饰器。
在我们试图创建一个新的抽象基类时,我们会像下面这样做。
from abc import ABCMeta, abstractmethod
class AbstractBettingStrategy(metaclass=ABCMeta):
slots = ()
@abstractmethod
def bet(self, hand):
return 1
@abstractmethod
def recordwin(self, hand):
pass
@abstractmethod
def recordloss(self, hand):
pass
@classmethod
def subclasshook(cls, subclass):
if cls is Hand:
if (any("bet" in B.dict for B in subclass.mro)
and any("recordwin" in B.dict for B in
subclass.mro)
and any("recordloss" in B.__dict for B in
subclass.__mro)
):
return True
return NotImplemented
这个类用ABCMeta类作为它的元类型;同时,它也实现了用于检查类型完整性的subclasshook()方法。这些方法提供了作为一个抽象基类应有的核心特性。
这个抽象基类使用abstractmethod装饰器定义了3个抽象方法,任何试图实现这个抽象类的子类都必须实现这3个方法。
subclasshook方法需要这3个方法都被实现。这样的做法似乎有一些过于严格了,因为一个很简单的下注方法不应该提供用来计算输赢的方法。
subclasshook依赖于Python内置的两个类特性:dict特性和mro特性。dict特性用于存储类中定义的方法名和特性名,这个特性中存储了类的主体。mro特性中记录了解析方法的顺序,这个特性记录了当前类层次结构的顺序。由于Python中允许多继承,因此有可能有许多基类,那么这些基类的顺序同时也决定了解析方法名的优先级。
下面是一个实现这个基类的例子。
class Simple_Broken(AbstractBettingStrategy):
def bet( self, hand ):
return 1
上面的类无法使用,因为它没有为3个抽象方法提供实现。
下面是当我们试图创建这个子类的实例时会发生的情况。
>>>simple= Simple_Broken()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Simple_Broken with
abstract methods record_loss, record_win
错误信息指出这个具体类是不完整的,下面是一个可以通过完整性测试的类。
class Simple(AbstractBettingStrategy):
def bet( self, hand ):
return 1
def record_win(self, hand):
pass
def record_loss(self, hand):
pass
我们可以创建这个类的一个实例并将它用于程序的模块中。
如上所述,bet()方法应该是唯一必须被实现的方法。另外两个方法中可以允许使用单独的pass语句作为默认实现。
