Python: совместное использование общего кода среди семейства скриптов

Я пишу семейство скриптов Python в рамках проекта; каждый script находится в подкаталоге проекта, например:

projectroot
  |
  |- subproject1
  |    |
  |    |- script1.main.py
  |    `- script1.merger.py
  |
  |- subproject2
  |    |
  |    |- script2.main.py
  |    |- script2.matcher.py
  |    `- script2.merger.py
  |
  `- subproject3
       |
       |- script3.main.py
       |- script3.converter.py
       |- script3.matcher.py
       `- script3.merger.py

Теперь некоторые из скриптов имеют общий код. Общий код лучше всего рассматривать как часть самого проекта, а не то, что я собирал бы отдельно, и выкладывал бы библиотеку, или удалял бы PYTHONPATH. Я мог бы разместить этот код в разных местах, например, в самом каталоге projectroot или в дочернем каталоге projectroot, который называется common (возможно).

Однако большинство способов, о которых я думал до сих пор, включают в себя создание пакетов из моих подпроектов с пустыми __init__.py файлами и использование относительного импорта (или избыточное возиться с sys.path в каждом подпроекте. Хуже, похоже, структура пакета вокруг этого семейства скриптов запускается из следующего предупреждения от отклоненного PEP-32122:

Внимание! Этот PEP был отклонен. Guido просматривает сценарии в пакете как анти-шаблон.

Если скрипты внутри пакета являются анти-патчавыми, как я могу настроить вещи таким образом, чтобы сохранить общий код в одном проекте? Или это приемлемый здесь модуль и пакетная система? Какой самый чистый подход? (FWIW Я предпочел бы иметь файл, такой как shared.py или common.py в корневом каталоге проекта, вместо того, чтобы сделать каталог утилиты, который является родным для "реальных" подпроектов.)

Ответы

Ответ 1

Я предлагаю положить тривиальные сценарии "запуска" на верхнем уровне вашего проекта и сделать каждую из подпроектов в пакеты. Модули в пакетах могут импортировать друг друга, или общий код может быть учтен в пакете common.

Здесь будет выглядеть структура, если предположить, что различные модули merger могут быть реорганизованы в общую версию:

projectroot
  |- script1.py # launcher scripts, see below for example code
  |- script2.py
  |- script3.py
  |
  |- common
  |    |- __init__.py
  |    |- merger.py # from other packages, use from ..common import merger to get this
  |
  |- subproject1
  |    |- __init__.py # this can be empty
  |    |- script1_main.py
  |
  |- subproject2
  |    |- __init__.py
  |    |- script2_main.py
  |    |- script2_matcher.py
  |
  |- subproject3
       |- __init__.py
       |- script3_main.py
       |- script3_converter.py
       |- script3_matcher.py

Скрипты запуска могут быть очень простыми:

from subproject1 import script1_main

if __name__ == "__main__":
    script1_main.main()

То есть все, что он делает, это импортировать соответствующий модуль "scriptN_main" и запустить в нем функцию. Использование простого script может также иметь некоторые небольшие преимущества для скорости запуска script, так как модуль main может иметь свой скомпилированный байт-код в кешированном файле .pyc, тогда как скрипты никогда не кэшируются.

Примечание. Я переименовал ваши модули, заменив _ символы на символы .. Вы не можете иметь . в идентификаторе (например, имя модуля), поскольку Python ожидает, что он укажет доступ к атрибуту. Это означает, что эти модули никогда не могут быть импортированы. (Я предполагаю, что это артефакт только файлов примеров, а не то, что у вас есть в вашем реальном коде.)

Ответ 2

Мое предпочтение было бы отдельным каталогом "bin" или "scripts" с подпроектами в виде библиотек/пакетов:

projectroot
  |
  |- scripts
  |
  |- lib
  |    |
  |    `- matcher.py
  |    `- merger.py
  |    `- subproject1
  |    `- subproject2
  |    `- subproject3

Идея ваших сценариев может ссылаться на любые подпроекты, необходимые как обычные пакеты. И ваши подпроекты также могут ссылаться друг на друга с импортом.

Затем у вас также может быть основной или общий script, который устанавливает для вас пакеты подпроектов, если это помогает.

Ответ 3

Пожалуйста, используйте setuptools, чтобы распределить библиотеки скриптов и:

например.

from setuptools import setup

setup(
    # other arguments here... (e.g. packages / package_dir)
    entry_points = {
        'console_scripts': [
            'script1 = subproject1.script1:main',
            'script2 = subproject2.script2:main',
        ],
    }
)

Если вы можете написать все свои коды в виде библиотек и не нуждаться в отдельных модулях, чтобы иметь свои точки входа, то это инструмент для вас. Если у вас есть скрипты, это тоже хорошо, но вам понадобится функция main, которую вы можете ссылаться (см. Пример выше)