Внедрение зависимостей в Python

Spread the love

Небольшая заметка о реализации внедрение зависимостей в Python с помощью библиотеки dependency_injector, от Shivam Aggarwal Dependency Injection: Python

Обзор

Внедрение зависимостей (Dependency Injection DI) – это метод разработки программного обеспечения для определения зависимостей между объектами. По сути, это процесс предоставления ресурса, который требуется фрагменту кода. Требуемый ресурс называется зависимостью.
При написании кода создаются различные классы и объекты . В большинстве случаев, чтобы выполнять свое предназначение эти классы зависят друг от друга. Классы, или лучший термин “Компоненты”, определяют, какие ресурсы им нужны и как их получить. DI управляет определением этих зависимых ресурсов и предоставляет способы их создания. Для реализации этого поведения используется “Контейнер зависимостей (Dependency Container)“. Он создает карту зависимостей для компонентов.
Так например, если объект A зависит от объекта B, объект A не должен напрямую импортировать объект B. Вместо этого объект A должен обеспечивать способ внедрения объекта B. Ответственность за создание объекта и внедрение зависимостей должно делегироваться внешнему коду.

Зачем использовать внедрение зависимостей?

  • Гибкость настраиваемых компонентов. Поскольку компоненты настраиваются извне, компонента могут быть создаваться совершенно различным способом (Тем самым улучшается контроль над структурой приложения).
  • Упрощение тестирования. Значительно упрощается создание фиктивных объектов и их интеграция с классами.
  • Увеличение согласованности компонентов. Позволяет уменьшается сложность компонентов, и повысить возможность повторного использования модулей.
  • Минимизация зависимостей. Поскольку зависимости четко определяются, их легче оптимизировать и уменьшить.

Реализация DI в Python

Внедрение зависимостей в Python мало отличается от общей реализации DI. Для этих целей в Python есть библиотека микро фреймворк, который называется dependency_injector. Этот пакет имеет две основные сущности: контейнеры и провайдеры.
Провайдеры описывают, как осуществляется доступ к объектам. Контейнеры – это просто набор провайдеров. Наиболее часто используемые типы провайдеров: Singleton, Configuration и Factory.

Пример

Следующий пример демонстрирует использование и реализацию DI в Python. Создайте файл с именем email_client.py, содержащий класс EmailClient, который зависит от объекта config.

class EmailClient(object):
    
    def __init__(self, config):
        self._config = config
        self.connect(self._config)
        
    def connect(self, config):
        # Implement function here
        pass

Создайте новый файл с именем email_reader.py, который содержит класс EmailReader и позже мы сделаем так что бы он станет зависить от объекта EmailClient.

class EmailReader(object):
    
    def __init__(self, client):
        try:
            self._client = client
        except Exception as e:
            raise e
            
    def read(self):
        # Implement function here
        pass

Теперь, чтобы определить эти выше упомянутые зависимости извне, создайте новый файл containers.py. Импортируйте пакет dependency_injector и классы для использования в DI.

from dependency_injector import providers, containers
from email_client import EmailClient
from email_reader import EmailReader

Добавьте класс Configs в этот файл. Этот класс будет являться контейнером с поставщиком конфигурации, который предоставляет все объекты конфигурации.

class Configs(containers.DeclarativeContainer):
    config = providers.Configuration('config')
    # other configs

Далее добавим еще один класс Clients. Этот класс тоже будет контейнером, определяющим все виды клиентов. EmailClient будет создаваться с помощью синглтон провайдера, создавая отдельный экземпляр этого класса и определяя его зависимость от объекта config.

class Clients(containers.DeclarativeContainer):
    email_client = providers.Singleton(EmailClient, Configs.config)
    # other clients 

Третий контейнер – это класс Readers, определяющий зависимость класса EmailReader от класса EmailClient.

class Readers(containers.DeclarativeContainer):
    email_reader = providers.Factory(EmailReader, client=Clients.email_client)
    # other readers 

В итоге файл containers.py должен выглядеть следующим образом:

from dependency_injector import providers, containers
from email_client import EmailClient
from email_reader import EmailReader

class Configs(containers.DeclarativeContainer):
    config = providers.Configuration('config')
    # other configs
    
class Clients(containers.DeclarativeContainer):
    email_client = providers.Singleton(EmailClient, Configs.config)
    # other clients
    
class Readers(containers.DeclarativeContainer):
    email_reader = providers.Factory(EmailReader, client=Clients.email_client)
    # other readers

Для запуска примера создайте файл main.py со следующим кодом.

from containers import Readers, Clients, Configs

if __name__ == "__main__":
    Configs.config.override({
        "domain_name": "imap.gmail.com",
        "email_address": "YOUR_EMAIL_ADDRESS",
        "password": "YOUR_PASSWORD",
        "mailbox": "INBOX"
    })
    email_reader = Readers.email_reader()
    print email_reader.read()

В файле main.py объект config переопределяется данным объектом условной конфигурации. Класс EmailReader был создан без создания экземпляра класса EmailClient в главном файле, исключая накладные расходы на его импорт или создание. об этом заботится файл containers.py.

Для рабочего примера кода, пожалуйста, обратитесь сюда.


Spread the love

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *