структурирование большого репозитория Python, чтобы не импортировать все - PullRequest
0 голосов
/ 10 ноября 2018

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

repo/
    __init__.py
    utils/
         __init__.py
         math.py
         readers.py             
         ...
    ...

Теперь наши __init__.py файлы настроены так, что мы можем сделать что-то вроде этого

from repo.utils import IniReader 

В этом примере repo/utils/__init__.py будет иметь

from .readers import IniReader, DatReader

Эта структура хорошо сработала для нас с точки зрения читабельности, но сейчас мы сталкиваемся с проблемами при попытке развертывания приложений.

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

from repo.utils import IniReader
if __name__ == '__main__':
    r = IniReader('blah.ini')
    print(r.fields)

Теперь from repo.utils import IniReader выполнит repo/utils/__init__.py, который в этом случае будет импортировать IniReader и DatReader. Давайте представим, что DatReader выглядит примерно так:

import numpy as np
import scipy
import tensorflow
from .math import transform

class DatReader():
...

, который соответствует PEP8, со всеми импортами в верхней части файла.

Проблема здесь в том, что DatReader требует некоторого тяжеловесного импорта (например, numpy, scipy, tenorflow - огромные библиотеки). Что еще хуже, from .math import transform может иметь что-то вроде from repo.contrib import lookup, которое затем попадает в repo/contrib/__init__.py, который запускает цепную реакцию и в итоге импортирует весь наш репозиторий.

Это действительно не было проблемой для всех нас, разработчиков, когда полная среда разработки встала, но теперь, когда мы пытаемся поставлять приложения (внутренне), этот адский импорт становится проблемой .

Есть ли стандартное решение этой проблемы? Мы говорили о том, чтобы просто оставить __init__.py пустым или просто не иметь всех импортируемых файлов в начале файла, как указано в PEP8. Оба эти решения идут с компромиссами, поэтому, если у кого-то есть предложения или рекомендации, я бы хотел их услышать.

Спасибо!

1 Ответ

0 голосов
/ 11 ноября 2018

Может быть полезно сделать небольшой шаг назад и взглянуть на фундаментальную проблему, с которой вы, похоже, столкнулись, а именно: " Как мне справиться с отсутствующими пакетами python на компьютерах пользователей?"

В основном есть две категории решений этой проблемы:

  1. Помогите сделать недостающие пакеты доступными на компьютере пользователя.
    • Вы можете распространять свой код в виде пакета , который пользователи могут установить с помощью pip. Просто включите спецификации зависимостей в ваш распределенный пакет, и pip предложит пользователям автоматически загружать и устанавливать любые отсутствующие пакеты.
    • Вы можете заморозить ваш код, т.е. преобразовать ваш код в автономное приложение, которое уже включает в себя все необходимые пакеты.
  2. Разделите зависимости вашего пакета на обязательные и необязательные и адаптируйте свой код так, чтобы отсутствие необязательного пакета не приводило к поломке всего кода.
    • Как вы уже заметили, вы можете санировать импорт на уровне модуля (то есть импорт в __init__.py файлах) так, чтобы дополнительные пакеты не загружались «преждевременно». В вашем случае это будет означать удаление DatReader импорта.
    • Как вы уже заметили, вы можете перемещать необязательный импорт пакетов внутри классов или функций, которые в них нуждаются. По стилю это не совсем оптимально, но сам код по-прежнему будет совершенно корректным. Обычно не имеет значения, что операторы импорта будут выполняться снова каждый раз при запуске функции, потому что фактический импорт будет выполняться только один раз .
    • Вы можете заключить импорт дополнительных пакетов в предложения try-кроме. Это предотвратит любые ошибки импорта (хотя, конечно, вы все равно столкнетесь с ошибкой, когда попытаетесь запустить класс или функцию, которая зависит от отсутствующего пакета).

Пример импорта в предложении try-кроме:

import warnings
try:
    import scipy
except ImportError:
    warnings.warn("The python package `scipy` could not be imported. As a result "
                  "the class `repo.utils.DatReader` will not be functional.")

Теперь вернемся к вашему первоначальному вопросу: «Есть ли стандартное решение этой проблемы?»: Я бы сказал, нет. Там нет ни одной золотой пули. Все решения имеют свои преимущества и недостатки, и вам придется решить, какое решение является оптимальным для вашей конкретной ситуации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...