在现代Python 3中,使用抽象基类实现接口要简单得多,它们的目的是作为插件扩展的接口协定。
创建接口/抽象基类:
from abc import ABC, abstractmethod
class AccountingSystem(ABC):
@abstractmethod
def create_purchase_invoice(self, purchase):
pass
@abstractmethod
def create_sale_invoice(self, sale):
log.debug('Creating sale invoice', sale)
创建一个普通的子类并覆盖所有抽象方法:
class GizmoAccountingSystem(AccountingSystem):
def create_purchase_invoice(self, purchase):
submit_to_gizmo_purchase_service(purchase)
def create_sale_invoice(self, sale):
super().create_sale_invoice(sale)
submit_to_gizmo_sale_service(sale)
您可以选择在抽象方法中使用通用实现,如所述create_sale_invoice()
,super()
在上述子类中显式调用它。
没有实现所有抽象方法的子类的实例化失败:
class IncompleteAccountingSystem(AccountingSystem):
pass
>>> accounting = IncompleteAccountingSystem()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
create_purchase_invoice, create_sale_invoice
通过将相应的注释与结合使用,您还可以拥有抽象属性,静态方法和类方法@abstractmethod
。
抽象基类非常适合实现基于插件的系统。可以通过访问所有类的所有导入子类__subclasses__()
,因此,如果从插件目录中加载所有类,importlib.import_module()
并且它们是基类的子类,则可以通过直接访问它们,__subclasses__()
并且可以确保对所有它们在实例化期间。
这是AccountingSystem
上面示例的插件加载实现:
...
from importlib import import_module
class AccountingSystem(ABC):
...
_instance = None
@classmethod
def instance(cls):
if not cls._instance:
module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
import_module(module_name)
subclasses = cls.__subclasses__()
if len(subclasses) > 1:
raise InvalidAccountingSystemError('More than one '
f'accounting module: {subclasses}')
if not subclasses or module_name not in str(subclasses[0]):
raise InvalidAccountingSystemError('Accounting module '
f'{module_name} does not exist or does not '
'subclass AccountingSystem')
cls._instance = subclasses[0]()
return cls._instance
然后,您可以通过访问会计系统插件对象 AccountingSystem
该类:
>>> accountingsystem = AccountingSystem.instance()
(受此PyMOTW-3帖子启发。)