← Назад к вопросам
Как функция может получать свои зависимости?
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-объектами
- Централизуй конфигурацию
❌ Не делай так:
- Жестко не кодируй зависимости
- Не скрывай зависимости в глубине функции
- Не создавай глобальные синглтоны без нужды
Правильная инъекция зависимостей — основа архитектуры, которая легко расширяется и тестируется.