Einführung
Für Java funktioniert Dependency Injection als reines OOP, dh Sie stellen eine zu implementierende Schnittstelle bereit und akzeptieren in Ihrem Framework-Code eine Instanz einer Klasse, die die definierte Schnittstelle implementiert.
Für Python können Sie jetzt auf die gleiche Weise vorgehen, aber ich denke, diese Methode war im Fall von Python zu viel Aufwand. Wie würden Sie es dann auf pythonische Weise implementieren?
Anwendungsfall
Angenommen, dies ist der Framework-Code:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
Der grundlegende Ansatz
Der naivste (und vielleicht der beste?) Weg besteht darin, zu verlangen, dass die externe Funktion in den FrameworkClass
Konstruktor eingegeben und dann von der do_the_job
Methode aufgerufen wird.
Rahmencode:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
Kundencode:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
Frage
Die Frage ist kurz. Gibt es eine besser verwendete pythonische Methode, um dies zu tun? Oder vielleicht Bibliotheken, die solche Funktionen unterstützen?
UPDATE: Konkrete Situation
Stellen Sie sich vor, ich entwickle ein Micro-Web-Framework, das die Authentifizierung mithilfe von Token übernimmt. Dieses Framework benötigt eine Funktion, um einige ID
vom Token erhaltene zu liefern und den Benutzer entsprechend zu erhalten ID
.
Offensichtlich weiß das Framework nichts über Benutzer oder eine andere anwendungsspezifische Logik, daher muss der Client-Code die User-Getter-Funktionalität in das Framework einfügen, damit die Authentifizierung funktioniert.
AttributeError
oderTypeError
anderweitig ausgelöst wird), aber ansonsten ist es dasselbe.abs
derABCMeta
Metaklasse mit@abstractmethod
Dekorateur und ohne manuelle Validierung zu tun . Ich möchte nur ein paar Optionen und Vorschläge erhalten. Der von Ihnen zitierte ist der sauberste, aber ich denke mit mehr Aufwand.Antworten:
Siehe Raymond Hettinger - Super als super! - PyCon 2015 für ein Argument zur Verwendung von Super- und Mehrfachvererbung anstelle von DI. Wenn Sie keine Zeit haben, das gesamte Video anzusehen, springen Sie zu Minute 15 (aber ich würde empfehlen, alles anzuschauen).
Hier ist ein Beispiel, wie Sie das, was in diesem Video beschrieben wird, auf Ihr Beispiel anwenden können:
Rahmencode:
class TokenInterface(): def getUserFromToken(self, token): raise NotImplementedError class FrameworkClass(TokenInterface): def do_the_job(self, ...): # some stuff self.user = super().getUserFromToken(...)
Kundencode:
class SQLUserFromToken(TokenInterface): def getUserFromToken(self, token): # load the user from the database return user class ClientFrameworkClass(FrameworkClass, SQLUserFromToken): pass framework_instance = ClientFrameworkClass() framework_instance.do_the_job(...)
Dies funktioniert, da das Python-MRO garantiert, dass die Client-Methode getUserFromToken aufgerufen wird (wenn super () verwendet wird). Der Code muss geändert werden, wenn Sie Python 2.x verwenden.
Ein zusätzlicher Vorteil besteht darin, dass dies eine Ausnahme auslöst, wenn der Client keine Implementierung bereitstellt.
Natürlich ist dies keine wirkliche Abhängigkeitsinjektion, sondern eine Mehrfachvererbung und Mixins, aber es ist eine pythonische Methode, um Ihr Problem zu lösen.
quelle
super()
:)Die Art und Weise, wie wir in unserem Projekt Abhängigkeitsinjektionen durchführen, ist die Verwendung der Injektionsbibliothek . Lesen Sie die Dokumentation . Ich empfehle dringend, es für DI zu verwenden. Es macht irgendwie keinen Sinn mit nur einer Funktion, macht aber viel Sinn, wenn Sie mehrere Datenquellen usw. usw. verwalten müssen.
Nach Ihrem Beispiel könnte es etwas Ähnliches sein wie:
# framework.py class FrameworkClass(): def __init__(self, func): self.func = func def do_the_job(self): # some stuff self.func()
Ihre benutzerdefinierte Funktion:
# my_stuff.py def my_func(): print('aww yiss')
Irgendwo in der Anwendung möchten Sie eine Bootstrap-Datei erstellen, die alle definierten Abhängigkeiten verfolgt:
# bootstrap.py import inject from .my_stuff import my_func def configure_injection(binder): binder.bind(FrameworkClass, FrameworkClass(my_func)) inject.configure(configure_injection)
Und dann könnten Sie den Code folgendermaßen verwenden:
# some_module.py (has to be loaded with bootstrap.py already loaded somewhere in your app) import inject from .framework import FrameworkClass framework_instance = inject.instance(FrameworkClass) framework_instance.do_the_job()
Ich befürchte, dies ist so pythonisch wie es nur geht (das Modul hat eine gewisse Python-Süße wie Dekoratoren, die nach Parametern usw. eingefügt werden können - überprüfen Sie die Dokumente), da Python keine ausgefallenen Dinge wie Schnittstellen oder Typhinweise hat.
Es wäre also sehr schwierig, Ihre Frage direkt zu beantworten . Ich denke, die wahre Frage ist: Hat Python eine native Unterstützung für DI? Und die Antwort lautet leider: Nein.
quelle
Vor einiger Zeit schrieb ich ein Mikroframework zur Abhängigkeitsinjektion mit dem Ziel, es zu Pythonic - Dependency Injector zu machen . So kann Ihr Code im Falle seiner Verwendung aussehen:
"""Example of dependency injection in Python.""" import logging import sqlite3 import boto.s3.connection import example.main import example.services import dependency_injector.containers as containers import dependency_injector.providers as providers class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" logger = providers.Singleton(logging.Logger, name='example') database = providers.Singleton(sqlite3.connect, ':memory:') s3 = providers.Singleton(boto.s3.connection.S3Connection, aws_access_key_id='KEY', aws_secret_access_key='SECRET') class Services(containers.DeclarativeContainer): """IoC container of business service providers.""" users = providers.Factory(example.services.UsersService, logger=Platform.logger, db=Platform.database) auth = providers.Factory(example.services.AuthService, logger=Platform.logger, db=Platform.database, token_ttl=3600) photos = providers.Factory(example.services.PhotosService, logger=Platform.logger, db=Platform.database, s3=Platform.s3) class Application(containers.DeclarativeContainer): """IoC container of application component providers.""" main = providers.Callable(example.main.main, users_service=Services.users, auth_service=Services.auth, photos_service=Services.photos)
Hier ist ein Link zu einer ausführlicheren Beschreibung dieses Beispiels - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
Hoffe es kann ein bisschen helfen. Für weitere Informationen, besuchen Sie bitte:
quelle
Platform
undServices
) fest codiert sind . Ist die Lösung, einen neuen Container für jede Kombination injizierbarer Bibliotheksklassen zu erstellen?Ich denke, dass DI und möglicherweise AOP aufgrund der typischen Präferenzen der Python-Entwickler im Allgemeinen nicht als Pythonic angesehen werden, sondern aufgrund der Sprachfunktionen.
Tatsächlich können Sie ein grundlegendes DI-Framework in <100 Zeilen mithilfe von Metaklassen und Klassendekoratoren implementieren .
Für eine weniger invasive Lösung können diese Konstrukte verwendet werden, um benutzerdefinierte Implementierungen in ein generisches Framework einzufügen.
quelle
Es gibt auch Pinject, einen Open-Source-Python-Abhängigkeitsinjektor von Google.
Hier ist ein Beispiel
>>> class OuterClass(object): ... def __init__(self, inner_class): ... self.inner_class = inner_class ... >>> class InnerClass(object): ... def __init__(self): ... self.forty_two = 42 ... >>> obj_graph = pinject.new_object_graph() >>> outer_class = obj_graph.provide(OuterClass) >>> print outer_class.inner_class.forty_two 42
Und hier ist der Quellcode
quelle
Die Abhängigkeitsinjektion ist eine einfache Technik, die Python direkt unterstützt. Es sind keine zusätzlichen Bibliotheken erforderlich. Die Verwendung von Typhinweisen kann die Klarheit und Lesbarkeit verbessern.
Rahmencode:
class UserStore(): """ The base class for accessing a user's information. The client must extend this class and implement its methods. """ def get_name(self, token): raise NotImplementedError class WebFramework(): def __init__(self, user_store: UserStore): self.user_store = user_store def greet_user(self, token): user_name = self.user_store.get_name(token) print(f'Good day to you, {user_name}!')
Kundencode:
class AlwaysMaryUser(UserStore): def get_name(self, token): return 'Mary' class SQLUserStore(UserStore): def __init__(self, db_params): self.db_params = db_params def get_name(self, token): # TODO: Implement the database lookup raise NotImplementedError client = WebFramework(AlwaysMaryUser()) client.greet_user('user_token')
Die
UserStore
Klassen- und Typhinweise sind für die Implementierung der Abhängigkeitsinjektion nicht erforderlich. Ihr Hauptzweck ist es, dem Kundenentwickler eine Anleitung zu geben. Wenn Sie dieUserStore
Klasse und alle Verweise darauf entfernen , funktioniert der Code weiterhin.quelle
Eine sehr einfache und pythonische Methode zur Durchführung der Abhängigkeitsinjektion ist importlib.
Sie können eine kleine Dienstprogrammfunktion definieren
def inject_method_from_module(modulename, methodname): """ injects dynamically a method in a module """ mod = importlib.import_module(modulename) return getattr(mod, methodname, None)
Und dann können Sie es verwenden:
myfunction = inject_method_from_module("mypackage.mymodule", "myfunction") myfunction("a")
In mypackage / mymodule.py definieren Sie myfunction
def myfunction(s): print("myfunction in mypackage.mymodule called with parameter:", s)
Sie können natürlich auch eine Klasse MyClass iso verwenden. die Funktion myfunction. Wenn Sie die Werte von Methodenname in einer Datei settings.py definieren, können Sie abhängig vom Wert der Einstellungsdatei verschiedene Versionen des Methodennamens laden. Django verwendet ein solches Schema, um seine Datenbankverbindung zu definieren.
quelle
Aufgrund der Python-OOP-Implementierung sind IoC und Abhängigkeitsinjektion in der Python-Welt keine Standardpraktiken. Aber der Ansatz scheint auch für Python vielversprechend.
Meine Lösung lautet also:
# Framework internal def MetaIoC(name, bases, namespace): cls = type("IoC{}".format(name), tuple(), namespace) return type(name, bases + (cls,), {}) # Entities level class Entity: def _lower_level_meth(self): raise NotImplementedError @property def entity_prop(self): return super(Entity, self)._lower_level_meth() # Adapters level class ImplementedEntity(Entity, metaclass=MetaIoC): __private = 'private attribute value' def __init__(self, pub_attr): self.pub_attr = pub_attr def _lower_level_meth(self): print('{}\n{}'.format(self.pub_attr, self.__private)) # Infrastructure level if __name__ == '__main__': ENTITY = ImplementedEntity('public attribute value') ENTITY.entity_prop
BEARBEITEN:
Sei vorsichtig mit dem Muster. Ich habe es in einem echten Projekt verwendet und es hat sich als nicht so gut erwiesen. Mein Beitrag auf Medium über meine Erfahrungen mit dem Muster.
quelle
Nachdem ich mit einigen DI-Frameworks in Python herumgespielt habe, habe ich festgestellt, dass sie sich etwas umständlich anfühlen, wenn man vergleicht, wie einfach es in anderen Bereichen wie .NET Core ist. Dies ist hauptsächlich auf die Verknüpfung über Dinge wie Dekorateure zurückzuführen, die den Code überladen und es schwierig machen, ihn einfach in ein Projekt einzufügen oder aus einem Projekt zu entfernen, oder auf die Verknüpfung basierend auf Variablennamen.
Ich habe kürzlich an einem Abhängigkeitsinjektions-Framework gearbeitet, das stattdessen Typanmerkungen verwendet, um die Injektion namens Simple-Injection durchzuführen. Unten ist ein einfaches Beispiel
from simple_injection import ServiceCollection class Dependency: def hello(self): print("Hello from Dependency!") class Service: def __init__(self, dependency: Dependency): self._dependency = dependency def hello(self): self._dependency.hello() collection = ServiceCollection() collection.add_transient(Dependency) collection.add_transient(Service) collection.resolve(Service).hello() # Outputs: Hello from Dependency!
Diese Bibliothek unterstützt die Lebensdauer und die Bindung von Diensten an Implementierungen.
Eines der Ziele dieser Bibliothek ist, dass es auch einfach ist, sie zu einer vorhandenen Anwendung hinzuzufügen und zu sehen, wie sie Ihnen gefällt, bevor Sie sie festschreiben, da Ihre Anwendung lediglich über geeignete Typisierungen verfügt. Anschließend erstellen Sie das Abhängigkeitsdiagramm unter den Einstiegspunkt und führen Sie es aus.
Hoffe das hilft. Weitere Informationen finden Sie unter
quelle