Какова лучшая структура проекта для приложения Python? [закрыто]

730

Представьте, что вы хотите разработать нетривиальное настольное (не веб) приложение для конечного пользователя на Python. Каков наилучший способ структурировать иерархию папок проекта?

Желаемыми функциями являются простота обслуживания, удобство IDE, пригодность для ветвления / слияния управления исходным кодом и простота создания инсталляционных пакетов.

В частности:

  1. Где вы положили источник?
  2. Где вы размещаете сценарии запуска приложения?
  3. Куда вы кладете проект IDE?
  4. Где вы положили блок / приемочные испытания?
  5. Где вы размещаете не-Python данные, такие как файлы конфигурации?
  6. Где вы размещаете не-Python исходники, такие как C ++ для бинарных модулей расширения pyd / so?
kbluck
источник

Ответы:

378

Не имеет большого значения. Все, что делает тебя счастливым, сработает. Там не так много глупых правил, потому что проекты Python могут быть простыми.

  • /scriptsили /binдля такого рода интерфейса командной строки
  • /tests для ваших тестов
  • /lib для ваших библиотек C-языка
  • /doc для большей части документации
  • /apidoc для сгенерированных Epydoc документов API.

А каталог верхнего уровня может содержать README, Config и еще много чего.

Трудный выбор - использовать /srcдерево или нет . Python не имеет различия между /src, /libи /binкак Java или C имеет.

Поскольку /srcкаталог верхнего уровня рассматривается некоторыми как бессмысленный, ваш каталог верхнего уровня может быть архитектурой верхнего уровня вашего приложения.

  • /foo
  • /bar
  • /baz

Я рекомендую поместить все это в каталог "name-of-my-product". Итак, если вы пишете приложение с именем quux, каталог, содержащий все эти вещи, называется /quux.

PYTHONPATHТаким образом, другой проект может включать /path/to/quux/fooповторное использование QUUX.fooмодуля.

В моем случае, поскольку я использую Komodo Edit, моя среда IDE представляет собой один файл .KPF. Я на самом деле помещаю это в /quuxкаталог верхнего уровня и не добавляю его в SVN.

С. Лотт
источник
23
Любые проекты с открытым исходным кодом Python вы бы порекомендовали имитировать их структуру каталогов?
Ланс Рашинг
4
Посмотрите на Джанго для хорошего примера.
S.Lott
33
Я не склонен считать Django хорошим примером - трюки с sys.path - это мгновенный DQ в моей книге.
Чарльз Даффи
18
re "tricks": Django добавляет родителя корневой папки проекта в sys.path, так что модули могут быть импортированы как "из проекта.app.module import klass", так и "из app.module import klass".
Джонатан Хартли
3
О, я люблю этот трюк и использую его сейчас. Я хочу поместить общий модуль в другой каталог, и я не хочу устанавливать модуль в масштабе всей системы, и при этом я не хочу просить людей изменить PYTHONPATH вручную. Если люди не предлагают что-то лучшее, я думаю, что это действительно самый чистый путь.
Юнвэй Ву
242

Согласно структуре файловой системы Жан-Поля Кальдероне проекта Python :

Project/
|-- bin/
|   |-- project
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- README
cmcginty
источник
23
Project/project/? Ах, второе имя пакета.
Сис Тиммерман
44
Как исполняемый файл в папке bin ссылается на модуль проекта? (Я не думаю, что синтаксис Python допускает ../включение оператора)
ThorSummoner
8
@ThorSummoner Simple. Вы устанавливаете пакет! ( pip install -e /path/to/Project)
Кролтан
22
Было бы здорово, если бы кто-нибудь скопировал образец этого макета с помощью hello.py и hello-test.py и сделал бы его доступным для нас, новичков.
jeremyjjbrown
8
@Bloke Ядро - это -eфлаг, который устанавливает пакет как редактируемый пакет, то есть устанавливает его как ссылки на фактическую папку проекта. Исполняемый файл может просто import projectиметь доступ к модулю.
Кролтан
232

Этот пост в блоге Жана-Пола Кальдероне обычно дается в качестве ответа в #python на Freenode.

Структура файловой системы проекта Python

Делать:

  • Назовите каталог что-то связанное с вашим проектом. Например, если ваш проект называется «Twisted», назовите каталог верхнего уровня для его исходных файлов Twisted. Когда вы делаете релизы, вы должны включать в себя номер версии суффикса: Twisted-2.5.
  • создайте каталог Twisted/binи поместите туда свои исполняемые файлы, если они у вас есть. Не давайте им .pyрасширение, даже если они исходные файлы Python. Не помещайте в них никакой код, кроме импорта и вызова основной функции, определенной где-то еще в ваших проектах. (Небольшая складка: поскольку в Windows интерпретатор выбирается по расширению файла, ваши пользователи Windows на самом деле хотят расширение .py. Поэтому, когда вы создаете пакет для Windows, вы можете захотеть добавить его. К сожалению, нет простого трюка с distutils, который Я знаю, как автоматизировать этот процесс. Учитывая, что в POSIX расширение .py - это всего лишь бородавка, тогда как в Windows это реальная ошибка, если в вашей пользовательской базе есть пользователи Windows, вы можете выбрать просто иметь .py расширение везде.)
  • Если ваш проект может быть выражен как один исходный файл Python, поместите его в каталог и назовите его как-нибудь связанный с вашим проектом. Например, Twisted/twisted.py. Если вам нужно несколько исходных файлов, вместо этого создайте пакет ( Twisted/twisted/с пустым Twisted/twisted/__init__.py) и поместите в него ваши исходные файлы. Например, Twisted/twisted/internet.py.
  • Поместите ваши модульные тесты в подпакет вашего пакета (обратите внимание - это означает, что указанная выше опция с единственным исходным файлом Python была хитростью - для ваших модульных тестов всегда нужен хотя бы один другой файл). Например, Twisted/twisted/test/. Конечно, сделайте пакет с Twisted/twisted/test/__init__.py. Поместите тесты в файлы, как Twisted/twisted/test/test_internet.py.
  • добавьте Twisted/READMEи Twisted/setup.pyобъясните и установите свое программное обеспечение соответственно, если вы чувствуете себя хорошо.

Не рекомендуется:

  • поместите ваш источник в каталог с именем srcили lib. Это затрудняет запуск без установки.
  • поместите свои тесты вне вашего пакета Python. Это затрудняет запуск тестов с установленной версией.
  • создайте пакет, который имеет только a, __init__.pyа затем поместите весь ваш код в __init__.py. Просто сделайте модуль вместо пакета, это проще.
  • попробуйте придумать магические хаки, чтобы Python мог импортировать ваш модуль или пакет, не добавляя пользователю каталог, содержащий его, в свой путь импорта (через PYTHONPATH или какой-либо другой механизм). Вы не будете правильно обрабатывать все случаи, и пользователи будут злиться на вас, если ваше программное обеспечение не работает в их среде.
Адриан
источник
25
Это было именно то, что мне было нужно. «НЕ пытайтесь придумать магические взломы, чтобы Python мог импортировать ваш модуль или пакет без необходимости добавления пользователем каталога, содержащего его, в путь импорта». Хорошо знать!
Джек О'Коннор
1
Дело в том, что здесь не упоминается важная часть документа, где его разместить.
lpapp
14
Смущен тем, что «поместите ваш исходный код в каталог с именем src или lib. Это затрудняет запуск без установки». Что бы установить? Это имя dir вызывает проблему или тот факт, что это вообще subdir?
Питер Эрлих
4
«Некоторые люди утверждают, что вы должны распределять свои тесты внутри самого модуля - я не согласен. Это часто увеличивает сложность для ваших пользователей; многие тестовые наборы часто требуют дополнительных зависимостей и контекстов времени выполнения». python-guide-pt-br.readthedocs.io/en/latest/writing/structure/...
эндолиты
2
«Это затрудняет запуск без установки». - в этом суть
Ник Т
123

Проверьте Open Sourcing Python Project правильный путь .

Позвольте мне извлечь часть макета проекта из этой превосходной статьи:

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

Давайте начнем с вершины. В большинстве проектов есть несколько файлов верхнего уровня (например, setup.py, README.md, needs.txt и т. Д.). Затем есть три каталога, которые должен иметь каждый проект:

  • Каталог документов, содержащий проектную документацию
  • Каталог с именем проекта, в котором хранится актуальный пакет Python
  • Тестовый каталог в одном из двух мест
    • Под каталогом пакета, содержащим тестовый код и ресурсы
    • Как автономный каталог верхнего уровня. Чтобы лучше понять, как должны быть организованы ваши файлы, вот упрощенный снимок макета для одного из моих проектов, sandman:
$ pwd
~/code/sandman
$ tree
.
|- LICENSE
|- README.md
|- TODO.md
|- docs
|   |-- conf.py
|   |-- generated
|   |-- index.rst
|   |-- installation.rst
|   |-- modules.rst
|   |-- quickstart.rst
|   |-- sandman.rst
|- requirements.txt
|- sandman
|   |-- __init__.py
|   |-- exception.py
|   |-- model.py
|   |-- sandman.py
|   |-- test
|       |-- models.py
|       |-- test_sandman.py
|- setup.py

Как вы можете видеть, есть несколько файлов верхнего уровня, каталог docs (сгенерированный является пустым каталогом, куда sphinx поместит сгенерированную документацию), каталог sandman и тестовый каталог в sandman.

Дэвид С. Бишоп
источник
4
Я делаю это, но более того: у меня есть Makefile верхнего уровня с целью 'env', который автоматизирует 'virtualenv env; ./env/bin/pip install -r needs.txt; ./env/bin/python setup.pyvelop ', а также обычно цель тестирования, которая зависит от env, устанавливает также тестовые зависимости и затем запускает py.test.
pjz
@pjz Не могли бы вы расширить свою идею? Вы говорите о том, чтобы поставить Makefileна тот же уровень, что и setup.py? Так что, если я вас правильно понял, make envавтоматизирует создание нового venvи установит в него пакеты ...?
Сент-Антарио,
@ Св. Антарио точно. Как уже упоминалось, у меня обычно также есть цель 'test' для запуска тестов, а иногда и цель 'release', которая просматривает текущий тег, создает колесо и отправляет его в pypi.
pjz
32

У "Python Packaging Authority" есть пример проекта:

https://github.com/pypa/sampleproject

Это пример проекта, который существует в качестве пособия к Руководству пользователя Python Packaging по проектам упаковки и распространения.

guettli
источник
+ тенденция к root/src/*структуре: github.com/pypa/sampleproject/commit/…
qrtLs
для получения информации о структуре проекта см. setuptools.readthedocs.io/_/downloads/en/latest/pdf
qrtLs
19

Попробуйте запустить проект, используя шаблон python_boilerplate . Он во многом соответствует лучшим практикам (например, здесь ), но лучше подходит, если вы захотите разделить свой проект на несколько яиц в какой-то момент (и, поверьте мне, с любыми проектами, кроме самых простых, вы это сделаете). Распространенная ситуация, когда вы должны использовать локально-модифицированную версию чужой библиотеки).

  • Где вы положили источник?

    • Для прилично больших проектов имеет смысл разделить источник на несколько яиц. Каждое яйцо будет отображаться как отдельная схема setuptools PROJECT_ROOT/src/<egg_name>.
  • Где вы размещаете сценарии запуска приложения?

    • Идеальный вариант - зарегистрировать скрипт запуска приложения как entry_pointодин из яиц.
  • Куда вы кладете проект IDE?

    • Зависит от IDE. Многие из них хранят свои вещи в PROJECT_ROOT/.<something>корне проекта, и это нормально.
  • Где вы положили блок / приемочные испытания?

    • Каждое яйцо имеет отдельный набор тестов, хранящихся в своем PROJECT_ROOT/src/<egg_name>/testsкаталоге. Я лично предпочитаю использовать их py.testдля запуска.
  • Где вы размещаете не-Python данные, такие как файлы конфигурации?

    • Это зависит. Могут быть разные типы данных, отличных от Python.
      • «Ресурсы» , то есть данные, которые должны быть упакованы в яйцо. Эти данные попадают в соответствующий каталог egg где-то в пространстве имен пакета. Его можно использовать через pkg_resourcesпакет из setuptools, или начиная с Python 3.7 через importlib.resourcesмодуль из стандартной библиотеки.
      • «Config-файлы» , то есть не-Python-файлы, которые должны рассматриваться как внешние по отношению к исходным файлам проекта, но должны быть инициализированы с некоторыми значениями при запуске приложения. Во время разработки я предпочитаю хранить такие файлы в PROJECT_ROOT/config. Для развертывания могут быть различные варианты. В Windows можно использовать %APP_DATA%/<app-name>/config, в Linux /etc/<app-name>или /opt/<app-name>/config.
      • Сгенерированные файлы , то есть файлы, которые могут быть созданы или изменены приложением во время выполнения. Я бы предпочел оставить их как во PROJECT_ROOT/varвремя разработки, так и /varво время развертывания Linux.
  • Где вы размещаете не-Python исходники, такие как C ++ для бинарных модулей расширения pyd / so?
    • В PROJECT_ROOT/src/<egg_name>/native

Документация обычно включается PROJECT_ROOT/docили PROJECT_ROOT/src/<egg_name>/doc(это зависит от того, считаете ли вы некоторые яйца отдельными крупными проектами). Некоторые дополнительные настройки будут в таких файлах, как PROJECT_ROOT/buildout.cfgи PROJECT_ROOT/setup.cfg.

KT.
источник
Спасибо за отличный ответ! Вы многое прояснили для меня! У меня только один вопрос: яйца могут быть вложенными?
Shookie
Нет, вы не можете «вкладывать» яйца в смысле хранения файлов .egg в других файлах .egg и надеяться, что это будет очень полезно [если только вы не собираетесь что-то действительно странное]. Однако вы можете создавать «виртуальные» яйца - пустые пакеты, которые не предоставляют никакого полезного кода, но включают другие пакеты в свои списки зависимостей. Таким образом, когда пользователь пытается установить такой пакет, он рекурсивно устанавливает много зависимых яиц.
КТ.
@KT можете ли вы немного рассказать о том, как вы обрабатываете сгенерированные данные? В частности, как вы (в коде) различаете разработку и развертывание? Я полагаю, у вас есть какая-то base_data_locationпеременная, но как вы ее правильно установите?
cmyr
1
Я предполагаю, что вы говорите о «данных времени выполнения» - то, что люди часто помещают в / var / packagename или ~ / .packagename / var, или еще много чего. В большинстве случаев этих вариантов по умолчанию достаточно, чтобы ваши пользователи не хотели менять их. Если вы хотите, чтобы это поведение было настроено, вариантов довольно много, и я не думаю, что существует единый подходящий метод. Типичные варианты: a) ~ / .packagename / configfile, b) экспорт MY_PACKAGE_CONFIG = / path / to / configfile c) параметры командной строки или параметры функции d) их сочетание.
КТ.
Обратите внимание, что обычно где-то есть одноэлементный класс Config, который обрабатывает вашу любимую логику загрузки конфигурации и, возможно, даже позволяет пользователю изменять настройки во время выполнения. В целом, однако, я думаю, что этот вопрос стоит отдельного вопроса (который, возможно, уже задавался где-то здесь).
КТ.
15

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

Что касается источников расширений, у нас есть каталог Code в trunk, который содержит каталог для python и каталог для различных других языков. Лично я в следующий раз больше склонен пытаться поместить любой код расширения в свой собственный репозиторий.

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

Джейсон Бейкер
источник
Ага. Я пытаюсь быть "Pythonic" по этому поводу: явное лучше, чем неявное. Иерархии каталогов читаются / проверяются больше, чем пишутся. И т.д ..
Эрик
10

Данные, не относящиеся к Python, лучше всего объединять в ваших модулях Python, используя package_dataподдержку в setuptools . Я настоятельно рекомендую использовать пакеты пространств имен для создания общих пространств имен, которые могут использовать несколько проектов - во многом как соглашение Java о размещении пакетов com.yourcompany.yourproject(и возможность иметь общее com.yourcompany.utilsпространство имен).

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

В отличие от некоторых других ответов здесь, я +1 от наличия srcкаталога верхнего уровня (с docи testкаталогами рядом). Конкретные соглашения для деревьев каталогов документации будут варьироваться в зависимости от того, что вы используете; Sphinx , например, имеет свои собственные соглашения, которые поддерживает его инструмент быстрого запуска.

Пожалуйста, пожалуйста, используйте setuptools и pkg_resources; это значительно упрощает использование другими проектами определенных версий вашего кода (и одновременную установку нескольких версий с разными файлами, не относящимися к коду, если вы используете package_data).

Чарльз Даффи
источник