Как сделать инъекцию зависимостей python-way? - PullRequest
12 голосов
/ 27 апреля 2010

В последнее время я много читал о python-way, поэтому мой вопрос

Как сделать инъекцию зависимостей python-way?

Я имею в виду обычные сценарии, когда, например, службе A необходим доступ к UserService для проверки авторизации.

Ответы [ 4 ]

14 голосов
/ 27 апреля 2010

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

subprocess.Popen = some_mock_Popen
result = subprocess.call(...)
assert some_mock_popen.result == result

subprocess.call() вызовет subprocess.Popen(), и мы можем его смоделировать, не вводя зависимость специальным образом. Мы можем просто заменить subprocess.Popen напрямую. (Это всего лишь пример; в реальной жизни вы бы сделали это гораздо более надежным способом.)

Если вы используете внедрение зависимостей для более сложных ситуаций, или когда нецелесообразно выполнять макетирование целых модулей или классов (поскольку, например, вы хотите макетировать только один конкретный вызов), тогда используйте атрибуты класса или глобальные переменные модулей для зависимостей. это обычный выбор. Например, учитывая my_subprocess.py:

from subprocess import Popen

def my_call(...):
    return Popen(...).communicate()

Вы можете легко заменить только вызов Popen, сделанный my_call(), назначив my_subprocess.Popen; это не повлияет на другие вызовы subprocess.Popen (но, конечно, заменит все вызовы my_subprocess.Popen). Аналогично, атрибуты класса:

class MyClass(object):
    Popen = staticmethod(subprocess.Popen)
    def call(self):
        return self.Popen(...).communicate(...)

При использовании таких атрибутов класса, которые редко требуются с учетом параметров, вам следует позаботиться об использовании staticmethod. Если вы этого не сделаете, а объект, который вы вставляете, является обычным функциональным объектом или дескриптором другого типа, например, свойством, которое делает что-то особенное при извлечении из класса или экземпляра, это будет делать неправильно. Хуже того, если бы вы использовали что-то, что прямо сейчас не является дескриптором (как, например, класс subprocess.Popen), это сработало бы сейчас, но если рассматриваемый объект изменился на обычную функцию в будущем, это сломается до замешательства.

Наконец, есть просто обратные вызовы; если вы просто хотите связать конкретный экземпляр класса с конкретным сервисом, вы можете просто передать сервис (или один или несколько методов сервиса) инициализатору класса и использовать его следующим образом:

class MyClass(object):
    def __init__(self, authenticate=None, authorize=None):
        if authenticate is None:
            authenticate = default_authenticate
        if authorize is None:
            authorize = default_authorize
        self.authenticate = authenticate
        self.authorize = authorize
    def request(self, user, password, action):
        self.authenticate(user, password)
        self.authorize(user, action)
        self._do_request(action)

...
helper = AuthService(...)
# Pass bound methods to helper.authenticate and helper.authorize to MyClass.
inst = MyClass(authenticate=helper.authenticate, authorize=helper.authorize)
inst.request(...)

При настройке таких атрибутов экземпляра вам никогда не придется беспокоиться о срабатывании дескрипторов, поэтому просто присваивайте функции (или классы, или другие вызываемые объекты или экземпляры).

1 голос
/ 07 апреля 2015

Как насчет этого «только для сеттера» рецепта впрыска? http://code.activestate.com/recipes/413268/

Это довольно питонно, использование протокола "дескриптора" с __get__() / __set__(), но довольно инвазивное, требующее замены всего вашего кода установки атрибута на экземпляр RequiredFeature, инициализированный str-именем Feature требуется.

0 голосов
/ 05 февраля 2018

@ Ответ Томаса Ваутерса полный и исчерпывающий. Просто добавлю к этому: обычно я предпочитаю самые простые подходы, те, которые не включают сложные рамки и подробные настройки. Поэтому я чаще всего использую свои зависимости в качестве аргументов конструктора.

Проблема в том, что шаблон, который я добавляю в код, чтобы гарантировать инициализацию каждой зависимости.

Будучи большим поклонником декораторов, чтобы убрать шаблонный код из области видимости функций, я просто создал декоратор для этого:

@ autowired Декоратор Python 3 для простого и понятного внедрения зависимостей:

  • функция вообще не должна знать о проводке
  • зависимости могут быть ленивыми инициализированы
  • вызывающая сторона может явно передать экземпляры зависимости, если это необходимо

Весь смысл декоратора в том, чтобы код был таким :

def __init__(self, *, model: Model = None, service: Service = None):
    if model is None:
        model = Model()

    if service is None:
        service = Service()

    self.model = model
    self.service = service
    # actual code

в это :

@autowired
def __init__(self, *, model: Model, service: Service):
    self.model = model
    self.service = service
    # actual code

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

Подход к декоратору очень минималистичен, но может случиться так, что полноценный фреймворк подойдет вам лучше. Для этого есть отличные модули, такие как Injector .

0 голосов
/ 02 февраля 2018

Я недавно выпустил DI-фреймворк для Python, который может вам здесь помочь. Я думаю, это довольно свежий взгляд на это, но я не уверен, насколько он «питоничен». Судите сами. Обратная связь очень приветствуется.

https://github.com/suned/serum

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...