← Назад к вопросам

Как функция может получать свои зависимости?

2.0 Middle🔥 241 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Получение функцией своих зависимостей (Dependency Injection)

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

1. Параметры функции (простейший способ)

Это основной и рекомендуемый способ:

def send_email(email: str, sender: str, smtp_client) -> bool:
    """Функция получает зависимость через параметр."""
    return smtp_client.send(to=email, from_=sender)

# Использование
smtp = SMTPClient()
send_email("user@example.com", "noreply@app.com", smtp)

Преимущества:

  • Явно видно, что нужно функции
  • Легко тестировать (подставить mock)
  • Нет скрытых зависимостей

2. Значения по умолчанию (для конфигурации)

def create_connection(db_url: str = None) -> Connection:
    if db_url is None:
        db_url = os.getenv("DATABASE_URL")
    return connect(db_url)

def process_data(db: Connection = None):
    if db is None:
        db = create_connection()
    # использование db

Минусы: скрывает зависимость, сложнее тестировать.

3. Контейнер зависимостей (Service Locator)

class Container:
    def __init__(self):
        self._services = {}
    
    def register(self, name: str, factory):
        self._services[name] = factory
    
    def get(self, name: str):
        return self._services[name]()

# Регистрация
container = Container()
container.register("db", lambda: Database(url="..."))
container.register("cache", lambda: RedisCache())

# Использование
def get_user(user_id: int, container: Container) -> User:
    db = container.get("db")
    cache = container.get("cache")
    return db.find_user(user_id, cache)

4. Декоратор для инъекции (Advanced)

from functools import wraps
from typing import Callable

class Injector:
    def __init__(self):
        self.bindings = {}
    
    def bind(self, name: str, value):
        self.bindings[name] = value
    
    def inject(self, func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Автоматически подставляем зависимости
            sig = inspect.signature(func)
            for param_name, param in sig.parameters.items():
                if param_name in self.bindings and param_name not in kwargs:
                    kwargs[param_name] = self.bindings[param_name]
            return func(*args, **kwargs)
        return wrapper

# Использование
injector = Injector()
injector.bind("logger", Logger())
injector.bind("db", Database())

@injector.inject
def process(data: str, logger=None, db=None):
    logger.info(f"Processing {data}")
    db.save(data)

process("hello")  # logger и db подставлены автоматически

5. Использование фреймворков (FastAPI, Dependency Injector)

FastAPI встроенный DI

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session

app = FastAPI()

def get_db() -> Session:
    """Зависимость для получения сессии БД."""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
    """Зависимость db автоматически инъектируется."""
    return db.query(User).filter(User.id == user_id).first()

Библиотека dependency-injector

from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()
    
    db = providers.Singleton(
        Database,
        url=config.db.url
    )
    
    user_repository = providers.Factory(
        UserRepository,
        db=db
    )

# Использование
container = Container()
container.config.db.url.set("postgresql://...")

user_repo = container.user_repository()
user = user_repo.find(1)

6. Контекст и Fixture (для тестирования)

import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_db():
    """Mock зависимость для тестов."""
    return Mock()

@pytest.fixture
def user_service(mock_db):
    """Сервис с mock-зависимостью."""
    return UserService(db=mock_db)

def test_create_user(user_service, mock_db):
    mock_db.save.return_value = None
    user_service.create("John")
    mock_db.save.assert_called_once()

7. Сравнение подходов

ПодходПростотаТестируемостьМощность
Параметры⭐⭐⭐⭐⭐⭐
Контейнер⭐⭐⭐⭐⭐⭐⭐
Декоратор⭐⭐⭐⭐
FastAPI Depends⭐⭐⭐⭐⭐⭐⭐⭐⭐

Лучшие практики

✅ Делай так:

  • Инъектируй зависимости через параметры
  • Используй типы для явности
  • Тестируй с mock-объектами
  • Централизуй конфигурацию

❌ Не делай так:

  • Жестко не кодируй зависимости
  • Не скрывай зависимости в глубине функции
  • Не создавай глобальные синглтоны без нужды

Правильная инъекция зависимостей — основа архитектуры, которая легко расширяется и тестируется.