Введение
Для Java внедрение зависимостей работает как чистое ООП, т. Е. Вы предоставляете интерфейс, который будет реализован, и в своем коде фреймворка принимаете экземпляр класса, реализующего определенный интерфейс.
Теперь для Python вы можете сделать то же самое, но я думаю, что в случае с Python этот метод был слишком накладным. Итак, как бы вы реализовали это питоническим способом?
Пример использования
Скажем, это код фреймворка:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
Основной подход
Самый наивный (и, может быть, лучший?) Способ - потребовать, чтобы внешняя функция была передана в FrameworkClass
конструктор, а затем вызывалась из do_the_job
метода.
Рамочный код:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
Код клиента:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
Вопрос
Вопрос короткий. Есть ли какой-нибудь лучший способ сделать это с помощью Pythonic? Или, может быть, какие-то библиотеки, поддерживающие такую функциональность?
ОБНОВЛЕНИЕ: конкретная ситуация
Представьте, что я разрабатываю микро-веб-фреймворк, который обрабатывает аутентификацию с помощью токенов. Этому фреймворку нужна функция для предоставления некоторых, ID
полученных из токена, и получения пользователя, соответствующего этомуID
.
Очевидно, что фреймворк ничего не знает о пользователях или какой-либо другой логике приложения, поэтому клиентский код должен внедрить в фреймворк функциональность получателя пользователя, чтобы аутентификация работала.
AttributeError
илиTypeError
), но в остальном это то же самое.abs
«sABCMeta
метакласса с@abstractmethod
декоратором, и никакой ручной проверки. Просто хочу получить пару вариантов и предложений. Тот, который вы процитировали, является наиболее чистым, но я думаю, что он требует дополнительных затрат.Ответы:
См. Raymond Hettinger - Супер считается супер! - PyCon 2015 за аргумент о том, как использовать супер- и множественное наследование вместо DI. Если у вас нет времени на просмотр всего видео, перейдите к 15 минуте (но я бы рекомендовал посмотреть все это).
Вот пример того, как применить то, что описано в этом видео, к вашему примеру:
Рамочный код:
class TokenInterface(): def getUserFromToken(self, token): raise NotImplementedError class FrameworkClass(TokenInterface): def do_the_job(self, ...): # some stuff self.user = super().getUserFromToken(...)
Код клиента:
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(...)
Это будет работать, потому что Python MRO гарантирует, что вызывается клиентский метод getUserFromToken (если используется super ()). Код придется изменить, если вы используете Python 2.x.
Дополнительным преимуществом здесь является то, что это вызовет исключение, если клиент не предоставит реализацию.
Конечно, это не совсем инъекция зависимостей, это множественное наследование и миксины, но это питонический способ решения вашей проблемы.
источник
super()
:)Внедрение зависимостей в нашем проекте выполняется с помощью inject lib. Ознакомьтесь с документацией . Я очень рекомендую использовать его для DI. Это не имеет смысла с одной функцией, но начинает иметь смысл, когда вам нужно управлять несколькими источниками данных и т. Д. И т. Д.
Следуя вашему примеру, это может быть что-то вроде:
# framework.py class FrameworkClass(): def __init__(self, func): self.func = func def do_the_job(self): # some stuff self.func()
Ваша пользовательская функция:
# my_stuff.py def my_func(): print('aww yiss')
Где-то в приложении вы хотите создать файл начальной загрузки, который отслеживает все определенные зависимости:
# bootstrap.py import inject from .my_stuff import my_func def configure_injection(binder): binder.bind(FrameworkClass, FrameworkClass(my_func)) inject.configure(configure_injection)
А затем вы можете использовать код следующим образом:
# 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()
Я боюсь, что это настолько питонический, насколько это возможно (модуль имеет некоторую сладость Python, например, декораторы для вставки по параметру и т. Д. - проверьте документацию), поскольку у python нет причудливых вещей, таких как интерфейсы или подсказки типов.
Так что ответить на ваш вопрос напрямую будет очень сложно. Я думаю, что истинный вопрос: есть ли в Python некоторая встроенная поддержка DI? К сожалению, ответ будет отрицательным.
источник
Некоторое время назад я написал микрофреймворк для внедрения зависимостей с амбициями сделать его Pythonic - Dependency Injector . Вот как может выглядеть ваш код в случае его использования:
"""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)
Вот ссылка на более подробное описание этого примера - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
Надеюсь, это немного поможет. Для получения дополнительной информации, пожалуйста, посетите:
источник
Platform
иServices
). Есть ли решение создать новый контейнер для каждой комбинации классов вводимых библиотек?Внедрение зависимостей - это простой метод, который Python поддерживает напрямую. Никаких дополнительных библиотек не требуется. Использование подсказок типа может улучшить ясность и читаемость.
Рамочный код:
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}!')
Код клиента:
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
класса и типа не требуется для реализации внедрения зависимостей. Их основная цель - дать рекомендации разработчику клиента. Если вы удалитеUserStore
класс и все ссылки на него, код все равно будет работать.источник
Я думаю, что DI и, возможно, АОП обычно не считаются Pythonic из-за типичных предпочтений разработчиков Python, а не из-за особенностей языка.
Фактически вы можете реализовать базовую структуру DI в <100 строк , используя метаклассы и декораторы классов.
Для менее инвазивного решения эти конструкции можно использовать для встраивания пользовательских реализаций в общую структуру.
источник
Существует также Pinject, инжектор зависимостей Python с открытым исходным кодом от Google.
Вот пример
>>> 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
А вот исходный код
источник
Очень простой и питонический способ внедрения зависимостей - importlib.
Вы можете определить небольшую функцию полезности
def inject_method_from_module(modulename, methodname): """ injects dynamically a method in a module """ mod = importlib.import_module(modulename) return getattr(mod, methodname, None)
И тогда вы можете использовать это:
myfunction = inject_method_from_module("mypackage.mymodule", "myfunction") myfunction("a")
В mypackage / mymodule.py вы определяете myfunction
def myfunction(s): print("myfunction in mypackage.mymodule called with parameter:", s)
Конечно, вы также можете использовать класс MyClass iso. функция myfunction. Если вы определяете значения имени метода в файле settings.py, вы можете загружать разные версии имени метода в зависимости от значения файла настроек. Django использует такую схему для определения подключения к базе данных.
источник
Из-за реализации ООП Python IoC и внедрение зависимостей не являются стандартной практикой в мире Python. Но этот подход кажется многообещающим даже для Python.
Итак, мое решение :
# 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
РЕДАКТИРОВАТЬ:
Будьте осторожны с выкройкой. Я использовал его в реальном проекте, и он показал себя не очень хорошо. Мой пост на Medium о моем опыте работы с узором.
источник
Поигравшись с некоторыми фреймворками DI в python, я обнаружил, что их использование было немного неуклюжим при сравнении того, насколько это просто в других сферах, таких как .NET Core. В основном это происходит из-за объединения с помощью таких вещей, как декораторы, которые загромождают код и затрудняют его простое добавление или удаление из проекта, или объединение на основе имен переменных.
Недавно я работал над структурой внедрения зависимостей, которая вместо этого использует аннотации ввода для выполнения инъекции, называемой Simple-Injection. Ниже простой пример
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!
Эта библиотека поддерживает время жизни службы и привязку служб к реализациям.
Одна из целей этой библиотеки заключается в том, что ее также легко добавить в существующее приложение и посмотреть, как она вам нравится, прежде чем делать ее, поскольку все, что для этого требуется, это чтобы ваше приложение имело соответствующие типы, а затем вы строите граф зависимостей на точку входа и запустите ее.
Надеюсь это поможет. Для получения дополнительной информации см.
источник