Подтвердить что ты не робот

Что такое питоновский способ инъекции зависимостей?

Введение

Для Java Injection Dependency работает как чистый OOP, т.е. вы предоставляете интерфейс, который должен быть реализован, и в вашем коде фрейма принимайте экземпляр класса, который реализует определенный интерфейс.

Теперь для 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(...)

Вопрос

Вопрос короткий. Существует ли более широко используемый питонический способ? Или, может быть, библиотеки, поддерживающие такую ​​функциональность?

ОБНОВЛЕНИЕ: Конкретная ситуация

Представьте себе, что я разрабатываю микро-веб-структуру, которая обрабатывает аутентификацию с использованием токенов. Эта структура нуждается в функции для доставки некоторого ID, полученного из токена, и получения соответствующего ему элемента ID.

Очевидно, что фреймворк ничего не знает о пользователях или какой-либо другой специфической для приложения логике, поэтому клиентский код должен внедрить функциональность пользовательского геттера в структуру, чтобы сделать работу аутентификации.

4b9b3361

Ответ 1

Смотрите Раймонд Хеттингер - Супер считается супер! - 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(...)

Это будет работать, потому что MRO Python гарантирует, что будет вызван клиентский метод getUserFromToken (если используется super()). Код должен измениться, если вы находитесь на Python 2.x.

Одним из дополнительных преимуществ здесь является то, что это вызовет исключение, если клиент не предоставит реализацию.

Конечно, это не настоящая инъекция зависимостей, это множественное наследование и микшины, но это путинский способ решить вашу проблему.

Ответ 2

Мы делаем инъекцию зависимостей в нашем проекте, используя 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 есть некоторая встроенная поддержка DI? И ответ, к сожалению: нет.

Ответ 3

Некоторое время назад я писал гиперкалькуляцию с зависимой инъекцией с целью сделать ее Pythonic - Инъектор зависимостей. Как ваш код может выглядеть в случае его использования:

"""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

Надеюсь, что это может немного помочь. Для получения дополнительной информации посетите:

Ответ 4

Я думаю, что DI и, возможно, AOP обычно не считаются Pythonic из-за типичных предпочтений разработчиков Python, а скорее из-за особенностей языка.

На самом деле вы можете реализовать базовую структуру DI в < 100 строк, используя метаклассы и декораторы классов.

Для менее инвазивного решения эти конструкции могут быть использованы для подгонки пользовательских реализаций в общую структуру.

Ответ 5

Благодаря реализации Python OOP IoC и внедрение зависимостей не являются распространенными практиками в мире Python. Тем не менее, подход показался многообещающим даже для 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         

Ответ 6

Существует также 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

А вот и исходный код