L'implementazione di interfacce con classi di base astratte è molto più semplice nel moderno Python 3 e hanno uno scopo come contratto di interfaccia per estensioni di plug-in.
Creare l'interfaccia / la classe base astratta:
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)
Creare una sottoclasse normale e sovrascrivere tutti i metodi astratti:
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)
Opzionalmente puoi avere un'implementazione comune nei metodi astratti come in create_sale_invoice()
, chiamandolo super()
esplicitamente nella sottoclasse come sopra.
L'istanza di una sottoclasse che non implementa tutti i metodi astratti ha esito negativo:
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
Puoi anche avere proprietà astratte, metodi statici e di classe combinando le corrispondenti annotazioni con @abstractmethod
.
Le classi di base astratte sono ottime per l'implementazione di sistemi basati su plugin. Tutte le sottoclassi importate di una classe sono accessibili tramite __subclasses__()
, quindi se si caricano tutte le classi da una directory di plug-in importlib.import_module()
e se si esegue la sottoclasse della classe di base, si ha accesso diretto ad esse tramite __subclasses__()
e si può essere certi che il contratto di interfaccia sia applicato per tutti loro durante l'istanza.
Ecco l'implementazione di caricamento plug-in per l' AccountingSystem
esempio sopra:
...
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
Quindi è possibile accedere all'oggetto plugin del sistema di contabilità attraverso la AccountingSystem
classe:
>>> accountingsystem = AccountingSystem.instance()
(Ispirato da questo post di PyMOTW-3 .)