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

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

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

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

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

4b9b3361

Ответ 1

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

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(...)

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

Ответ 2

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

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

Ответ 3

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

https://github.com/suned/serum

Ответ 4

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

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

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

@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.