Wprowadzenie
W przypadku języka Java Dependency Injection działa jako czysty OOP, tj. Udostępniasz interfejs do zaimplementowania, aw kodzie struktury akceptujesz instancję klasy, która implementuje zdefiniowany interfejs.
Teraz w przypadku Pythona możesz zrobić to samo, ale myślę, że ta metoda była zbyt dużym narzutem w przypadku Pythona. Jak więc zaimplementowałbyś to w Pythonie?
Przypadek użycia
Powiedz, że to jest kod ramowy:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
Podejście podstawowe
Najbardziej naiwnym (a może najlepszym?) Sposobem jest wymaganie, aby funkcja zewnętrzna została dostarczona do FrameworkClass
konstruktora, a następnie wywoływana z do_the_job
metody.
Kod ramowy:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
Kod klienta:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
Pytanie
Pytanie jest krótkie. Czy istnieje lepszy, powszechnie używany sposób w Pythonie, aby to zrobić? A może biblioteki obsługujące taką funkcjonalność?
AKTUALIZACJA: konkretna sytuacja
Wyobraź sobie, że tworzę mikro framework sieciowy, który obsługuje uwierzytelnianie za pomocą tokenów. Ta struktura wymaga funkcji, która będzie dostarczać część ID
uzyskaną z tokenu i pobierać użytkownika odpowiadającego temu ID
.
Oczywiście framework nie wie nic o użytkownikach ani żadnej innej logice specyficznej dla aplikacji, więc kod klienta musi wstrzyknąć do frameworka funkcję pobierającą użytkownika, aby uwierzytelnianie działało.
AttributeError
inaczejTypeError
zostanie podniesiony lub), ale poza tym jest tak samo.abs
„sABCMeta
metaklasa z@abstractmethod
dekoratora i bez ręcznego sprawdzania poprawności. Chcę tylko uzyskać kilka opcji i sugestii. Ten, który zacytowałeś, jest najczystszy, ale myślę, że jest większy.Odpowiedzi:
Zobacz Raymond Hettinger - Super uważany za super! - PyCon 2015 w celu omówienia sposobu korzystania z dziedziczenia super i wielokrotnego zamiast DI. Jeśli nie masz czasu na obejrzenie całego filmu, przejdź do minuty 15 (ale polecam obejrzenie całości).
Oto przykład, jak zastosować to, co opisano w tym filmie, w swoim przykładzie:
Kod ramowy:
class TokenInterface(): def getUserFromToken(self, token): raise NotImplementedError class FrameworkClass(TokenInterface): def do_the_job(self, ...): # some stuff self.user = super().getUserFromToken(...)
Kod klienta:
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(...)
To zadziała, ponieważ Python MRO zagwarantuje, że zostanie wywołana metoda klienta getUserFromToken (jeśli używana jest super ()). Kod będzie musiał się zmienić, jeśli korzystasz z Pythona 2.x.
Dodatkową korzyścią jest to, że spowoduje to wyjątek, jeśli klient nie zapewni implementacji.
Oczywiście nie jest to tak naprawdę wstrzykiwanie zależności, jest to wielokrotne dziedziczenie i miksowanie, ale jest to Pythonowy sposób rozwiązania problemu.
źródło
super()
:)Sposób, w jaki wykonujemy wstrzyknięcie zależności w naszym projekcie, polega na użyciu pliku inject lib. Sprawdź dokumentację . Bardzo polecam używanie go do DI. To trochę nie ma sensu z tylko jedną funkcją, ale zaczyna mieć dużo sensu, gdy musisz zarządzać wieloma źródłami danych itp.
Idąc za twoim przykładem, może to być coś podobnego do:
# framework.py class FrameworkClass(): def __init__(self, func): self.func = func def do_the_job(self): # some stuff self.func()
Twoja funkcja niestandardowa:
# my_stuff.py def my_func(): print('aww yiss')
Gdzieś w aplikacji chcesz utworzyć plik bootstrap, który śledzi wszystkie zdefiniowane zależności:
# bootstrap.py import inject from .my_stuff import my_func def configure_injection(binder): binder.bind(FrameworkClass, FrameworkClass(my_func)) inject.configure(configure_injection)
Następnie możesz konsumować kod w ten sposób:
# 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()
Obawiam się, że jest to tak Pythonowe, jak to tylko możliwe (moduł ma trochę słodyczy Pythona, takich jak dekoratory do wstrzykiwania przez parametr itp. - sprawdź dokumentację), ponieważ Python nie ma wymyślnych rzeczy, takich jak interfejsy lub podpowiedzi typu.
Dlatego udzielenie bezpośredniej odpowiedzi na pytanie byłoby bardzo trudne. Myślę, że prawdziwe pytanie brzmi: czy Python ma natywne wsparcie dla DI? Niestety, odpowiedź brzmi: nie.
źródło
Jakiś czas temu napisałem microframework typu dependency injection z ambicją uczynienia go Pythonic - Dependency Injector . Tak może wyglądać Twój kod w przypadku jego użycia:
"""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)
Oto link do bardziej szczegółowego opisu tego przykładu - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
Mam nadzieję, że to może trochę pomóc. Aby uzyskać więcej informacji prosimy odwiedzić:
źródło
Platform
iServices
). Czy rozwiązaniem jest utworzenie nowego kontenera dla każdej kombinacji wstrzykiwanych klas bibliotecznych?Wstrzykiwanie zależności to prosta technika, którą Python obsługuje bezpośrednio. Nie są wymagane żadne dodatkowe biblioteki. Korzystanie ze wskazówek dotyczących tekstu może poprawić przejrzystość i czytelność.
Kod ramowy:
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}!')
Kod klienta:
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')
UserStore
Klasa i rodzaj podpowiedzi nie są wymagane dla wykonania iniekcji zależność. Ich głównym celem jest udzielenie wskazówek deweloperowi klienta. Jeśli usunieszUserStore
klasę i wszystkie odniesienia do niej, kod nadal działa.źródło
Myślę, że DI i prawdopodobnie AOP nie są ogólnie uważane za Pythonic ze względu na typowe preferencje programistów Pythona, a raczej cechy języka.
W rzeczywistości możesz zaimplementować podstawowy framework DI w <100 liniach , używając metaklas i dekoratorów klas.
Aby uzyskać mniej inwazyjne rozwiązanie, te konstrukcje mogą być używane do podłączania niestandardowych implementacji do ogólnej struktury.
źródło
Istnieje również Pinject, wstrzykiwacz zależności Pythona typu open source od Google.
Oto przykład
>>> 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
A oto kod źródłowy
źródło
Bardzo prostym i Pythonowym sposobem na wstrzykiwanie zależności jest importlib.
Możesz zdefiniować małą funkcję użytkową
def inject_method_from_module(modulename, methodname): """ injects dynamically a method in a module """ mod = importlib.import_module(modulename) return getattr(mod, methodname, None)
A potem możesz go użyć:
myfunction = inject_method_from_module("mypackage.mymodule", "myfunction") myfunction("a")
W mypackage / mymodule.py definiujesz moją funkcję
def myfunction(s): print("myfunction in mypackage.mymodule called with parameter:", s)
Możesz oczywiście użyć klasy MyClass iso. funkcja myfunction. Jeśli zdefiniujesz wartości nazwy metody w pliku settings.py, możesz załadować różne wersje nazwy metody w zależności od wartości pliku ustawień. Django używa takiego schematu do definiowania połączenia z bazą danych.
źródło
Ze względu na implementację Python OOP, IoC i wstrzykiwanie zależności nie są standardowymi praktykami w świecie Pythona. Ale podejście wydaje się obiecujące nawet dla Pythona.
Więc moje rozwiązanie to:
# 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
EDYTOWAĆ:
Uważaj na wzór. Użyłem go w prawdziwym projekcie i pokazał się niezbyt dobrze. Mój post na Medium o moich doświadczeniach z wzorem.
źródło
Po zabawie z niektórymi frameworkami DI w Pythonie stwierdziłem, że ich użycie jest trochę niezgrabne, porównując, jak proste jest to w innych dziedzinach, takich jak .NET Core. Wynika to głównie z łączenia za pomocą elementów takich jak dekoratory, które zaśmiecają kod i utrudniają po prostu dodanie go do projektu lub usunięcie go z projektu lub łączenie na podstawie nazw zmiennych.
Niedawno pracowałem nad strukturą iniekcji zależności, która zamiast tego używa adnotacji do pisania, aby wykonać wstrzyknięcie o nazwie Simple-Injection. Poniżej znajduje się prosty przykład
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!
Ta biblioteka obsługuje okresy istnienia usług i usługi powiązania z implementacjami.
Jednym z celów tej biblioteki jest to, że łatwo jest dodać ją do istniejącej aplikacji i zobaczyć, jak Ci się podoba, zanim się do niej zdecydujesz, ponieważ wszystko, czego wymaga, to aplikacja, która ma odpowiednie typy, a następnie zbudujesz wykres zależności w punkt wejścia i uruchom go.
Mam nadzieję że to pomoże. Aby uzyskać więcej informacji, zobacz
źródło