Das Implementieren von Schnittstellen mit abstrakten Basisklassen ist in modernem Python 3 viel einfacher und dient als Schnittstellenvertrag für Plug-In-Erweiterungen.
Erstellen Sie die Schnittstelle / abstrakte Basisklasse:
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)
Erstellen Sie eine normale Unterklasse und überschreiben Sie alle abstrakten Methoden:
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)
Sie können optional eine gemeinsame Implementierung in den abstrakten Methoden wie in create_sale_invoice()
haben und diese super()
wie oben explizit in der Unterklasse aufrufen .
Die Instanziierung einer Unterklasse, die nicht alle abstrakten Methoden implementiert, schlägt fehl:
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
Sie können auch abstrakte Eigenschaften, statische und Klassenmethoden verwenden, indem Sie entsprechende Anmerkungen mit kombinieren @abstractmethod
.
Abstrakte Basisklassen eignen sich hervorragend für die Implementierung von Plugin-basierten Systemen. Auf alle importierten Unterklassen einer Klasse kann über __subclasses__()
zugegriffen werden. Wenn Sie also alle Klassen aus einem Plugin-Verzeichnis mit laden importlib.import_module()
und wenn sie die Basisklasse unterklassifizieren, haben Sie direkten Zugriff auf sie über __subclasses__()
und Sie können sicher sein, dass der Schnittstellenvertrag für alle erzwungen wird sie während der Instanziierung.
Hier ist die Implementierung zum Laden des Plugins für das AccountingSystem
obige Beispiel:
...
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
Anschließend können Sie über die AccountingSystem
Klasse auf das Plugin-Objekt des Buchhaltungssystems zugreifen :
>>> accountingsystem = AccountingSystem.instance()
(Inspiriert von diesem PyMOTW-3-Beitrag .)