Все зависит от ситуации. Например, если вы используете внедрение зависимостей в целях тестирования - так что вы можете легко что-то смоделировать - вы часто можете вообще отказаться от внедрения: вместо этого вы можете смоделировать модуль или класс, который вы в противном случае внедрили бы:
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(...)
При настройке таких атрибутов экземпляра вам никогда не придется беспокоиться о срабатывании дескрипторов, поэтому просто присваивайте функции (или классы, или другие вызываемые объекты или экземпляры).