Как в Python уменьшить количество аргументов в методе __init__?

«Как в Python уменьшить количество аргументов в методе __init__?» — вопрос из категории Паттерны, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Большое количество аргументов в __init__ — это "code smell", который ухудшает читаемость и усложняет создание объектов. Для решения этой проблемы применяются следующие подходы:

  1. Группировка аргументов в класс данных (Data Transfer Object). Это наиболее предпочтительный и "питонический" способ. Связанные параметры объединяются в отдельный объект-конфигурацию. Для этого идеально подходят dataclasses из стандартной библиотеки или модели Pydantic.

    Пример с dataclasses:

    from dataclasses import dataclass
    
    @dataclass
    class DatabaseConfig:
        host: str
        port: int
        username: str
        password: str
    
    class DatabaseConnection:
        def __init__(self, config: DatabaseConfig):
            self.config = config
            # ... логика подключения
    
    # Создание объекта становится чистым и типизированным
    db_config = DatabaseConfig("localhost", 5432, "user", "pass")
    connection = DatabaseConnection(db_config)
  2. Использование паттерна "Строитель" (Builder). Этот паттерн позволяет пошагово конструировать сложный объект. Он особенно полезен, когда у объекта много опциональных параметров.

    class ReportBuilder:
        def __init__(self):
            self._params = {}
    
        def with_title(self, title: str):
            self._params['title'] = title
            return self
    
        def with_format(self, fmt: str):
            self._params['format'] = fmt
            return self
    
        def build(self):
            return Report(**self._params)
    
    # Использование
    report = ReportBuilder().with_title("Sales").with_format("PDF").build()
  3. Прием аргументов через `kwargs`.** Этот подход гибок, но делает код менее явным и усложняет статическую проверку типов. Его следует использовать с осторожностью, обязательно добавляя валидацию.

    class User:
        def __init__(self, **kwargs):
            required_fields = {"name", "email"}
            if not required_fields.issubset(kwargs.keys()):
                raise ValueError("Отсутствуют обязательные поля")
            self.name = kwargs.get("name")
            self.email = kwargs.get("email")
            self.is_active = kwargs.get("is_active", True)