Как стать автором
Обновить

Комментарии 38

А пробовали zope.interface?
Это скорее про моделирование интерфейсов. А вообще zope — это попытка сделать энтерпрайз в питоне. Во всяком случае так было раньше.
Там как раз про DI (читать про adapters). Там нет ничего про энтерпрайз, особенно сейчас и особенно в zope.interface.
Расскажите пожалуйста, для чего собственно эти инжекты нужны в питоне? В статье как-то не увидел практических применений
Скорее, зачем вообще нужно внедрение зависимостей, потому что сама концепция вне языка.

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

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

Самой концепции много лет. Подробнее можно почитать на википедии, хотя информация там довольно запутанная. Нужно ли вообще внедрение зависимостей? Да, это не только мое мнение. Посмотрите на количество фреймворков, в т.ч. и от Гугла: Google Guice, Spring, Google Pinject, Objective-C Typhoon и многие другие.
Вот меня интересовало в рамках Python, т.к. тот же mock делается легко и просто и, как по мне, сильно нагляднее, чем это сделано здесь.
А как объекты узнают про ваш мок? Напишите, пожалуйста, как бы вы переписали код с тестом из самого первого примера с User + Mailer.
# conf.py
if sys.environ.get('ENV_TYPE') == 'PROD':
    db = ...
    mailer = ...
else: 
    db = ...
    mailer = ...

# main.py
import conf

foo(conf.db)
bar(conf.mailer)

По сути у вас делается то же самое, только запутанней.
Скорее тоже самое, только без двуликого conf.py, который одновременно изящное решение в маленьком проекте и костылик в большом, потому как инициализируется неочевидным способом.
Идея инжектов (блин! как похож он на zope.interface с его концепцией адаптеров), чтобы сделать код менее связным, не надо заранее за пользователя класса User решать в каком контексте он будет запускать свой код и прописывать ветки conf.py
Конфиг или не конфиг — не важно. Важно, что есть уровень абстракции, единая сущность, на которую ссылаются все заинтересованные объекты. При необходимости поменять настройки, достаточно заменить реализацию, лежащую за уровнем абстракции, и код поменяется везде. Это старая и довольно простая идея, и для неё не нужно придумывать новые имена, а уж тем более изобретать библиотеки.

В Java костыль в виде DI нужен, чтобы побороть (якобы) статическую типизацию и считывать настройки из конфиг файлов (Spring IoC вообще в каком-нибудь другом контексте используется?). Код на Python не нужно компилировать, поэтому конфиги можно писать прямо в .py файлах, утиная типизация избавляет от необходимости пораждать тонны интерфейсов, ну а динамическая диспетчеризация по хеш-таблицам позволяет в любой момент менять любые сущности вплоть до модулей. Ну и для чего тогда нужна отдельная запутанная процедура инъекции зависимостей?
При конфигурировании можно использовать разные конфиг файлы, как минимум.
А при моках, эм, стандартно библиотекой mock.
Не совсем понял, зачем объектам знать, что они замочены?
Чтобы обращаться к моку, а не к обычному классу.
вы меня прям в тупик загнали, зачем вам обращаться к моку и почему ваш код должен знать, мок это или нет?
Вы всего лишь мокаете, для примера, метод send() и потом проверяете, что его вызывали с теми параметрами, что надо.
Обращаться к моку нужно чтобы подменить вызов реального объекта (в примере базы или мэйлера) вызовом мока. Чтобы подменить вызов нужен способ подменить вызываемую сущность. Самый простой способ это делать — внедрять эту зависимость извне объекта.
Самый простой способ подменить вызов это, извините, подменить вызов:

foo.send = mock_send

А все эти огороды с Dependency Injection нужны там, где гибкость языка не позволяет простые вещи делать простыми. Извините, если кого обидел.
А еще проще DI реализуется с помощью банальных импортов :)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Нафига козе боян?

А если серьезно — в питоне это не нужно, потому как ту отсутствует строгая типизация, и бороться с ней с помощью инъекций бессмысленно. Это только порождает дополнительную сложность, а это не python way, на мой взгляд.

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

Кроме этого можно пачить классы в рантайме, по примеру гевентов, без проблем!
НЛО прилетело и опубликовало эту надпись здесь
Потому что все DI библиотеки пытаются решить проблему инъекции внутри класса. У питона такой проблемы не существует.

class User(object):
    db = Fake() # db connection

    def do_some():
        do.save(True)

.....

User.db = RedisDb('localhost:1234') # explicit injection or 'setattr' can be used for text based config files
user = User()
user.do_some() # the redis db is used



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

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

А вообще хороший ответ по теме на стековерфлове: Why is IoC / DI not common in Python
НЛО прилетело и опубликовало эту надпись здесь
Я к тому, что на питоне эта задача достаточна тривиальна (если она действительно нужна) и решается одним методом в 5 строк:

class Container:
    def __init__(self, system_data):
        for component_name, component_class, component_args in system_data:
            if type(component_class) == types.ClassType:
                args = [self.__dict__[arg] for arg in component_args]
                self.__dict__[component_name] = component_class(*args)
            else:
                self.__dict__[component_name] = component_class


Оригинал: Inversion of Control

В статье не хватает примеров и не показана использование инъекции внутренних объектов в локальной области (иначе задача вообще тривиальная, и сводиться к использованию глобального конфига) из за этого и возникло непонимание. Т.е. если нужен глобальный инжекшн то конфигов вполне достаточно.
Так же не показан в общем метод реализации автора, в чем его смысл.
НЛО прилетело и опубликовало эту надпись здесь
IoC — принцип, DI — реализация, что не так?
В примере с контейнером как раз показана DI
НЛО прилетело и опубликовало эту надпись здесь
Извините, а вы на пайтоне пишите?
НЛО прилетело и опубликовало эту надпись здесь
потому как ту отсутствует строгая типизация

Это в Питоне-то типизация не строгая?
бороться с ней с помощью инъекций бессмысленно

DI это не борьба со строгой типизацией, это, как правило, борьба с высокой связанностью.

А ваш пример без DI должен выглядеть примерно так:
class User(object):
    def db_some():
        RedisDb('localhost:1234').save(True)

Патчите
Я не против DI. Я к тому что для этого особых фреймворков в питоне не нужно и все реализуется достаточно тривиально. По поводу глобальной инъекции уже указали на конфиги как самое простое и очевидное решение.

Типизация как раз и приводит к высокой связанности. На пайтоне можно писать вообще без типов и внешних связей и делать инекцию атрибутов по мере выполнение(пример выше с юзером) — это уже само по себе DI.

По моему тут просто нужно показать простые и тривиальные методы реализации DI и решения. Тогда было бы более понятно что и для чего.
Типизация со связанностью никак не связана. Связанность показывает число внешних сущностей в широком смысле слова, плюс степень их конкретизации — в узком, от которых зависит рассматриваемая сущность. Ваш User, например, связан с классом Fake, пускай и не жестко, мой с классом RedisDb и жестко. Вот если бы свой класс сделали без инициализации свойства db, то его связанность резко бы упала, всё что требовалось бы — инициализация извне этого свойства, объектом реализующим метод save() (по сути реализующий интерфейс сохранения, в статически типизированных языках это мог быть какой нибудь экземпляр наследника класса AbstractStorage или объект, реализующий интерфейс Persistentable).

Ну а так, да, согласен, что без тривиальных методов с указанием на их недостатки пост выглядит не совсем понятным.
Fake — класс-фальшивка, так обычно пишут в примерах. Никак не связан.
Короче в пайтоне проблем с инъекцией нет — вам нужно, делайте хоть к классу, хоть к инстансу. И многие так делают, когда это надо — это python way и в этом нет никаких проблем или сложностей.
И да, было бы наверно более интересно обсудить эти методы и рецепты.
Ну сначала вы утверждали, что инъекции вообще не нужны в связи с нестрогой типизацией :) Я же пытался донести, что а) нужны независимо от типизации, б) типизация и связанность ортогональны и в) в Питоне типизация строгая (TypeError: unsupported operand type(s) for +: 'int' and 'str')
Не инъекции — а библиотеки и «костыли» которые надо устанавливать и поддерживать. Лучше просто примеры. Строгая динамическая, моя ошибка. Имел ввиду статическая.
За целый день так не не отписался кто-то понимающий что такое DI и почему он в питоне тоже нужен? Эх :(
Ага, не удалась моя попытка для людей что-то новое и хорошее сделать. Мне уже в комментах предлагают даже модули патчить в рантайме и методы классов подменять для тестов. Брр.
Так а чем оно хорошее то? Есть стандартные рабочие решения, опробованные на больших долгоживущих проектах, а вы приходите и говорите, что всё прежнее — фуфло, и на самом деле надо вот так. При этом продвигаете, во-первых, стороннюю библиотеку (которая уже дополнительная зависимость), а во-вторых, нарушает сразу 2 пункта из Python Zen — «явное лучше неявного» и «простое лучше сложного».

Просто вы зачем-то пытаетесь перенести некоторые концепции из Java в Python. При этом игнорируете тот факт, что эти концепции были разработаны для обхода ограничений конкретного языка, и в другом языке могут просто не иметь смысла (так же, как, например, паттерн Стратегия не имеет в Питоне смысла просто потому что функции и так являются объектами первого рода).
Если что, «чем оно хорошее» — это не риторический вопрос (как и в другом моём комментарии выше). Если вы действительно уверены, что так лучше, то расскажите почему, поделитесь опытом. Никто же не против нового и хорошего. Но ведь надо ещё и знать, зачем оно.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории