Яка найкраща структура проекту для програми Python? [зачинено]


730

Уявіть, що ви хочете розробити нетривіальний додаток для кінцевих користувачів (не веб) на Python. Який найкращий спосіб структурувати ієрархію папок проекту?

Бажані особливості - це простота обслуговування, зручність використання IDE, придатність для розгалуження / об'єднання / управління об'єктом джерела, а також проста генерація встановлених пакетів.

Зокрема:

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

Відповіді:


376

Це не має великого значення. Все, що робить вас щасливим, буде працювати. Дурних правил не так багато, тому що проекти Python можуть бути простими.

  • /scriptsабо /binдля цього типу інтерфейсу командного рядка
  • /tests для ваших тестів
  • /lib для ваших бібліотек С-мови
  • /doc для більшості документації
  • /apidoc для документів, створених API Epydoc.

І каталог верхнього рівня може містити 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модуля.

У моєму випадку, оскільки я використовую Комодо редагування, мій IDE-макет є єдиним .KPF-файлом. Я фактично ставлю це до /quuxкаталогу верхнього рівня і пропускаю додавання його до SVN.


23
Будь-які проекти з відкритим кодом python, яким ви рекомендуєте наслідувати їх структуру каталогу?
Lance Rushing

4
Подивіться на Джанго для хорошого прикладу.
S.Lott

33
Я не схильний вважати Джанго хорошим прикладом - грати трюки з sys.path - це миттєвий DQ в моїй книзі.
Чарльз Даффі

18
повторні "трюки": Django додає батьківський файл кореневої папки проекту до sys.path, щоб модулі можна було імпортувати як "з projekt.app.module import klass", так і "з класу імпорту класу app.module".
Джонатан Хартлі

3
О, я люблю цю хитрість і зараз її використовую. Я хочу помістити спільний модуль в інший каталог, і я не хочу встановлювати модуль у всій системі, а також не хочу просити людей змінювати PYTHONPATH вручну. Якщо люди не запропонують щось краще, я думаю, що це насправді найчистіший шлях.
Yongwei Wu

242

Відповідно до структури Файлової системи Жана-Пола Кальдероне проекту Python :

Project/
|-- bin/
|   |-- project
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- README

23
Project/project/? Ага, друга - назва пакета.
Cees Timmerman

44
як виконуваний файл у папці bin посилається на модуль проекту? (Я не думаю, що синтаксис python дозволяє ../включити оператор include)
ThorSummoner

8
@ThorSummoner Простий. Ви встановлюєте пакет! ( pip install -e /path/to/Project)
Кролтан

22
Було б дивовижно, якби хтось застебнув зразок цього макета за допомогою hello.py та hello-test.py і зробив його доступним для нас нових користувачів.
jeremyjjbrown

8
@Bloke Ядро - це -eпрапор, який встановлює пакунок як редагований пакет, тобто встановлює його як посилання на фактичну папку проекту. Тоді виконуваний файл може просто import projectмати доступ до модуля.
Кролтан

231

Ця публікація Жана-Пола Кальдероне зазвичай дається як відповідь у #python на Freenode.

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

Зробіть:

  • назвіть у каталозі щось, що стосується вашого проекту. Наприклад, якщо ваш проект названо "Скручений", назвіть каталог верхнього рівня для його вихідних файлів Twisted. Коли ви робите релізи, ви повинні включати в себе номер версії суфікса: Twisted-2.5.
  • створіть каталог Twisted/binі покладіть туди свої виконавчі файли, якщо вони є. Не давайте їм .pyрозширення, навіть якщо вони є вихідними файлами Python. Не вкладайте в них жодного коду, крім імпорту та виклику до головної функції, визначеної десь у ваших проектах. (Незначна зморшка: оскільки в Windows інтерпретатор вибирається за допомогою розширення файлу, користувачі Windows насправді хочуть розширення .py. Отже, коли ви пакуєте для Windows, ви можете додати його. На жаль, не існує простого трюку для дистрибутивів, який Я знаю про автоматизацію цього процесу. Враховуючи, що на 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. Це ускладнює запуск тестів на встановленій версії.
  • створити пакет , який тільки має , __init__.pyа потім помістити весь код в __init__.py. Просто зробіть модуль замість пакета, це простіше.
  • спробуйте придумати магічні хаки, щоб змусити Python імпортувати ваш модуль чи пакет без того, щоб користувач додав каталог, що містить його, до свого шляху імпорту (через PYTHONPATH або якийсь інший механізм). Ви не будете правильно розбиратись у всіх випадках, і користувачі будуть злитися на вас, коли ваше програмне забезпечення не працює в їх оточенні.

25
Це саме те, що мені було потрібно. "НЕ намагайтеся придумати магічні хаки, щоб Python міг імпортувати ваш модуль або пакет без того, щоб користувач додав каталог, що містить його, до шляху імпорту." Добре знати!
Джек О'Коннор

1
Справа в тому, що це не згадує про важливу документальну частину проекту, де її розмістити.
lpapp

14
Плутати з приводу того, що "помістіть джерело в каталог, який називається src або lib. Це ускладнює запуск без встановлення." Що було б встановлено? Чи викликає проблему ім'я режисури чи факт, що це взагалі субдір?
Пітер Ерліх

3
"Деякі люди запевняють, що ви повинні розповсюджувати свої тести в самому модулі - я не погоджуюся. Це часто збільшує складність для ваших користувачів; багато тестових наборів часто вимагають додаткових залежностей і контекстів виконання". python-guide-pt-br.readthedocs.io/en/latest/writing/structure / ...
ендоліти

2
"Це ускладнює запуск без встановлення." - у цьому справа
Нік Т

123

Перевірте правильний шлях відкритого пошуку проекту Python .

Дозвольте мені викласти проект макету частини цієї чудової статті:

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

Почнемо з верху. У більшості проектів є декілька файлів верхнього рівня (наприклад, setup.py, README.md, вимоги.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

Як ви бачите, є деякі файли верхнього рівня, каталог документів (згенерований порожній каталог, куди сфінкс буде поміщати згенеровану документацію), каталог пісочних людей і тестовий каталог під Sandman.


4
Я це роблю, але тим більше: у мене є Makeple-файл з топлевом з ціллю 'env', яка автоматизує 'virtualenv env; ./env/bin/pip install -r вимоги.txt; ./env/bin/python setup.py razvija ', а також зазвичай' тестова 'ціль, яка залежить від env, а також встановлює тестові залежності, а потім запускає py.test.
pjz

@pjz Чи можете ви, будь ласка, розширити свою ідею? Ви говорите про те, щоб поставити Makefileна тому ж рівні, що і setup.py? Тож якщо я зрозумів, що ви правильно make envавтоматизуєте створення нового venvта встановлення пакетів у нього ...?
St.Antario

@ St.Antario точно. Як вже згадувалося, у мене зазвичай є «тестова» ціль для запуску тестів, а іноді і ціль «випуску», яка дивиться на поточний тег і створює колесо і відправляє його в pypi.
pjz

32

"Орган упаковки Python" має зразокпроекту:

https://github.com/pypa/sampleproject

Це зразок проекту, який існує як підручник до Посібника користувача програми Python Packaging, Посібник з упаковки та розповсюдження проектів.


+ тенденція до root/src/*структури: github.com/pypa/sampleproject/commit/…
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, можуть бути різними.
      • "Ресурси" , тобто дані, які повинні бути упаковані в яйце. Ці дані надходять у відповідний каталог яєць, десь у просторі імен пакунків. Він може використовуватися через 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.


Дякую за чудову відповідь! Ти мені прояснив багато речей! У мене просто одне питання: чи можна вкладати яйця?
Shookie

Ні, ви не можете "вкладати" яйця в сенсі зберігання .egg-файлів в інших .egg-файлах і сподіваючись, що це буде корисно [якщо ви не щось дійсно дивне]. Однак ви можете зробити "віртуальні" яйця - порожні пакети, які не містять корисного коду, але перерахуйте інші пакунки в їхніх списках залежностей. Таким чином, коли користувач намагається встановити такий пакет, він буде рекурсивно встановлювати багато залежних яєць.
КТ.

@KT чи можете ви детально розібратися, як обробляти генеровані дані? Зокрема, як ви (в коді) розрізняєте розробку та розгортання? Я думаю, у вас є якась base_data_locationзмінна, але як її правильно встановити?
смр

1
Я припускаю, що ви говорите про "дані часу виконання" - те, що люди часто ставлять під / var / packagename або ~ / .packagename / var, чи що. Більшість випадків цих варіантів є достатніми як за замовчуванням, які ваші користувачі не хочуть змінювати. Якщо ви хочете дозволити налаштування такої поведінки, варіантів досить багато, і я не думаю, що існує одна найкраща практика. Типові варіанти: a) ~ / .packagename / configfile, b) експорт MY_PACKAGE_CONFIG = / шлях / до / configfile c) параметри командного рядка або параметри функцій d) комбінація цих.
КТ.

Зауважте, що десь звичайно є однотонний клас Config, який обробляє вашу улюблену логіку завантаження конфігурації для вас і, можливо, навіть дозволяє користувачеві змінювати налаштування під час виконання. Взагалі, однак, я думаю, що це питання, яке варто окремого питання (яке, можливо, було б задано десь тут).
КТ.

15

На мій досвід, це лише питання ітерації. Покладіть свої дані та код куди б ви думали, що вони йдуть. Швидше за все, ви все одно помилитесь. Але як тільки ви отримаєте краще уявлення про те, як саме складаються речі, ви зможете зробити такі здогадки набагато кращими.

Що стосується джерел розширення, у нас є каталог коду під магістраллю, який містить каталог для python та каталог для різних інших мов. Особисто я більше схильний намагатися ввести наступний раз будь-який код розширення у власне сховище.

З урахуванням сказаного, я повертаюся до свого початкового пункту: не робіть з цього занадто великої угоди. Покладіть його десь, що, здається, працює на вас. Якщо ви знайдете щось, що не працює, його можна (і слід) змінити.


Так. Я намагаюся бути "пітонічним" з цього приводу: явне краще, ніж неявне. Герархії каталогів читаються / перевіряються більше, ніж написано. Etc ..
eric

10

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

Повторне розгалуження та об'єднання, якщо ви використовуєте достатньо хорошу систему управління джерелом, вона буде обробляти злиття навіть через перейменування; Базар особливо хороший у цьому.

На відміну від деяких інших відповідей тут, я поставив +1, щоб мати srcкаталог верхнього рівня (із docта testкаталогіми поряд). Конкретні умови для дерев каталогів документації залежать від того, що ви використовуєте; Наприклад, у Сфінкса є власні конвенції, які підтримує його інструмент швидкого запуску.

Будь ласка, використовуйте налаштування і pkg_resources; це значно спрощує іншим проектам покладатися на конкретні версії вашого коду (і на кілька версій, які одночасно встановлюються з різними файлами без коду, якщо ви використовуєте package_data).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.