Как разрабатывать модуль, который использует сторонние библиотеки, но не имеет зависимостей при установке?

Использовать подход "vendoring":
  • Суть: Упаковать необходимые сторонние библиотеки внутрь вашего модуля.
  • Механизм: Копируете нужные библиотеки в поддиректорию внутри вашего проекта (например, `my_module/vendor/`).
  • Использование: Динамически добавляете этот каталог в `sys.path` во время выполнения вашего модуля.
  • Плюсы: Нет зависимостей при установке, все нужное уже включено. Контроль над версиями сторонних библиотек.
  • Минусы: Увеличение размера пакета, потенциальные конфликты версий, ручное обновление сторонних библиотек.

Пример:


import sys
import os

vendor_dir = os.path.join(os.path.dirname(__file__), 'vendor')
if vendor_dir not in sys.path:
    sys.path.insert(0, vendor_dir)

# Теперь можно импортировать библиотеки из vendor_dir, например:
# import requests
  

Вопрос интересный и требует нестандартного подхода. Обычно, при использовании сторонних библиотек, мы декларируем их в файле requirements.txt или setup.py, что приводит к необходимости их установки при установке нашего модуля. Но есть несколько способов обойти это требование, хоть и с некоторыми оговорками:

  1. Vendoring (включение зависимостей в сам модуль):

    Самый распространенный метод. Суть в том, чтобы скопировать код необходимых библиотек непосредственно в ваш модуль. Создается, например, папка vendor внутри вашего модуля и туда помещается код нужных библиотек (или только необходимых частей). В вашем коде импортируются эти библиотеки относительно этой папки:

    
          # Модуль: my_module/my_module.py
          # Зависимость: requests
          # Структура:
          # my_module/
          #   __init__.py
          #   my_module.py
          #   vendor/
          #     requests/
          #       __init__.py
          #       api.py
          #       ...
          #
          # В my_module.py:
          from .vendor.requests import api # Или whatever modules you need
    
          def my_function():
              return api.get("https://www.example.com")
          

    Плюсы: Изоляция от системных библиотек, полный контроль над используемыми версиями, отсутствие зависимостей при установке. Минусы: Увеличение размера модуля, необходимость вручную обновлять vendored-библиотеки, возможные проблемы с лицензиями (обязательно проверяйте лицензии используемых библиотек).

    Важное замечание: Нужно убедиться, что лицензия vendored библиотеки позволяет это делать! Обычно это разрешено, но всегда нужно проверять.

  2. Lazy Loading и Runtime Dependency Check (динамическая загрузка и проверка зависимостей во время выполнения):

    Использовать importlib для динамической загрузки библиотек только при необходимости. Перед загрузкой проверять, установлена ли библиотека в системе. Если нет, gracefully обрабатывать ситуацию (например, выводить предупреждение и отключать функциональность, требующую эту библиотеку).

    
          import importlib
          import sys
    
          def use_library():
              try:
                  library = importlib.import_module("requests") # Или другое название
                  # Используем библиотеку
                  response = library.get("https://www.example.com")
                  print(response.status_code)
              except ImportError:
                  print("WARNING: The 'requests' library is not installed. Functionality will be limited.")
                  # Альтернативный код, если библиотека не установлена
    
          use_library()
    
          

    Плюсы: Не требует установки зависимостей при установке модуля, уменьшение размера модуля, гибкость. Минусы: Увеличение сложности кода, возможные ошибки во время выполнения, зависимость от наличия библиотеки в системе.

  3. Использовать только стандартную библиотеку Python:

    Если функциональность сторонней библиотеки можно реализовать с использованием стандартной библиотеки Python (например, вместо requests использовать urllib), то это самый простой способ избежать зависимостей. Однако, это может потребовать больше усилий и не всегда возможно.

  4. Optional Dependencies (дополнительные зависимости) через `extras_require` в `setup.py` или `pyproject.toml`:

    Хотя это и добавляет зависимости, это делает их *опциональными*. Пользователь устанавливает основной модуль, а потом, *если* ему нужна функциональность, зависящая от внешней библиотеки, он устанавливает модуль с "экстрой".

    
            # В setup.py
            from setuptools import setup, find_packages
    
            setup(
                name='my_module',
                version='0.1.0',
                packages=find_packages(),
                extras_require={
                    'optional_feature': ['requests']
                },
            )
    
            # Установка с "экстрами":
            # pip install my_module[optional_feature]
    
           

    В коде нужно будет делать проверку наличия библиотеки, как и в случае lazy loading, но при этом, если пользователь *захочет* использовать эту функцию, он сможет легко установить необходимые зависимости.

Выбор конкретного метода зависит от:

  • Размера и сложности сторонних библиотек.
  • Требований к размеру и скорости установки вашего модуля.
  • Допустимости лицензий сторонних библиотек для vendoring.
  • Ваших предпочтений в стиле кодирования.

Важно: При любом подходе тщательно документируйте свои решения и предупреждайте пользователей о возможных ограничениях.

На собеседовании важно показать понимание этих альтернатив и умение оценивать их плюсы и минусы в конкретном контексте.

0