Implementar interfaces con clases base abstractas es mucho más simple en Python 3 moderno y sirven como un contrato de interfaz para extensiones de plug-in.
Cree la interfaz / clase base abstracta:
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)
Cree una subclase normal y anule todos los métodos abstractos:
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)
Opcionalmente, puede tener una implementación común en los métodos abstractos como en create_sale_invoice()
, llamándolo super()
explícitamente en la subclase como anteriormente.
La creación de instancias de una subclase que no implementa todos los métodos abstractos falla:
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
También puede tener propiedades abstractas, métodos estáticos y de clase combinando las anotaciones correspondientes con @abstractmethod
.
Las clases base abstractas son excelentes para implementar sistemas basados en complementos. Se puede acceder a todas las subclases importadas de una clase __subclasses__()
, por lo que si carga todas las clases desde un directorio de complementos importlib.import_module()
y si subclasan la clase base, tiene acceso directo a ellas a través de __subclasses__()
y puede estar seguro de que el contrato de interfaz se aplica para todos ellos durante la instanciación.
Aquí está la implementación de carga de complementos para el AccountingSystem
ejemplo anterior:
...
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
Luego puede acceder al objeto de complemento del sistema de contabilidad a través de la AccountingSystem
clase:
>>> accountingsystem = AccountingSystem.instance()
(Inspirado en esta publicación de PyMOTW-3 ).
raise NotImplementedError
es loshow
que debería ser el cuerpo, ¡no tiene sentido criar un genérico completamenteException
cuando Python define uno incorporado perfectamente específico! -)