Вообще говоря, как структурированы проекты (Python)?

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

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

В текущей форме проект выглядит следующим образом:

COPYING
README.md
SailQt.pyw                    (Should program be called from here ...)
sailqt/
    __init__.py               (This holds a __version__ string)
    SailQt.pyw                (... or here?)
    gui/
        __init__.py
        MainWindow.py         (This needs access to a __version__ string)
        MainWindow_rc.py
        OptionsWidget.py
        ui_MainWindow.py
        ui_OptionsWidget.py
    resources/
        __init__.py
        database.db
        generate_gui.py
        MainWindow.ui
        MainWindow.qrc
        OptionsWidget.ui
        icons/
            logo.png

Дальнейшее уточнение. resources содержит все .ui файлы, сделанные в Qt Designer. Это файлы XML, которые описывают GUI. Они могут быть преобразованы в сценарии Python с помощью терминала, который я встроил в generate_gui.py. То же самое касается файлов .qrc. generate_gui.py помещает автогенерируемые файлы в папку gui с префиксом ui_ или суффикс _rc. database.db в настоящее время пуст, но в конечном итоге будет использоваться для хранения цен и всего.

MainWindow.py и OptionsWidget.py - это файлы Python, содержащие объекты с тем же именем, минус суффикс .py. MainWindow содержит OptionsWidget на поверхности отображения. Оба объекта используют соответствующие файлы ui и rc.

SailQt.pyw - это файл, который создает экземпляр MainWindow, сообщает ему, чтобы он показывал себя, а затем сообщает (Py) Qt, чтобы ввести его цикл и взять его оттуда. В основном это похоже на файл .exe большого количества графических приложений, поскольку он представляет собой небольшой файл, который запускает программу.

Мое первоначальное предположение заключалось в размещении SailQt.pyw внутри папки sailqt. Но тогда MainWindow.py внезапно понадобился доступ к строке __version__. Единственный способ, которым я мог понять, как это сделать, - переместить SailQt.pyw в корневую папку моего проекта и позволить MainWindow.py import sailqt.__version__. Но, учитывая, что в первый раз мне пришлось перетасовать вещи и переделать строки в большинстве файлов, чтобы объяснить эту крошечную перетасовку, я решил просто спросить здесь.

Мои вопросы достаточно ясны:

  • Как, в общем, проекты Python структурированы? Эта ссылка pydoc была полезной, но для меня это больше похоже на модуль, чем на то, что фактически выполняется пользователем.
  • Правильно ли я получил вышеуказанное структурирование?
  • Бонусные баллы за ответ, так как это немного не по теме. Почему я могу сделать import os, а затем делать такие вещи, как os.system("sudo rm -rf /"), но я не могу делать такие вещи, как import sailqt, а затем делать sailqt.gui.generate_gui.generate()?

Ответ 1

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

Главное, чтобы понять, что каталог текущего запуска script автоматически добавляется в начало sys.path. Поэтому, если вы разместите main.py script (то, что вы в настоящее время вызываете SailQt.pyw) за пределами своего пакета в каталоге контейнеров верхнего уровня, это гарантирует, что импорт пакетов будет всегда работать независимо от того, где script выполняется из.

Таким образом, минимальная стартовая структура может выглядеть так:

project/
    main.py
    package/
        __init__.py
        app.py
        mainwindow.py

Теперь, поскольку main.py должен находиться за пределами каталога пакета python верхнего уровня, он должен содержать только минимальный код кода (достаточно для запуска программы). Учитывая указанную выше структуру, это будет означать не более того:

if __name__ == '__main__':

    import sys
    from package import app
    sys.exit(app.run())

Модуль app будет содержать большую часть фактического кода, необходимого для инициализации программы и настройки gui, который будет импортироваться следующим образом:

from package.mainwindow import MainWindow

и эта же форма полного описания импорта может использоваться из любого места с пакетом. Так, например, с этой несколько более сложной структурой:

project/
    main.py
    package/
        __init__.py
        app.py
        mainwindow.py
        utils.py
        dialogs/
            search.py

модуль search может импортировать функцию из модуля utils следующим образом:

 from package.utils import myfunc

По конкретной проблеме доступа к строке __version__: для программы PyQt вы можете поместить следующее в начало модуля app:

    QtGui.QApplication.setApplicationName('progname')      
    QtGui.QApplication.setApplicationVersion('0.1')

а затем позже введите имя/версию следующим образом:

    name = QtGui.qApp.applicationName()
    version = QtGui.qApp.applicationVersion()

Другие проблемы с вашей текущей структурой в основном связаны с сохранением разделения между файлами кода и файлами ресурсов.

Во-первых: дерево пакетов должно содержать только файлы кода (т.е. модули python). Файлы ресурсов относятся к директории проекта (то есть вне пакета). Во-вторых: файлы, содержащие код, сгенерированный из ресурсов (например, pyuic или pyrcc), вероятно, должны идти в отдельный подпакет (это также облегчает для вашего средства управления версиями исключение). Это приведет к созданию общей структуры проекта следующим образом:

project/
    db/
        database.db
    designer/
        mainwindow.ui
    icons/
        logo.png
    LICENSE
    Makefile
    resources.qrc
    main.py
    package/
        __init__.py
        app.py
        mainwindow.py
        ui/
            __init__.py
            mainwindow_ui.py
            resources_rc.py

Здесь Makefile (или эквивалент) отвечает за создание файлов ui/rc, компиляцию модулей python, установку/удаление программы и т.д. Ресурсы, необходимые программе во время выполнения (например, файл базы данных), необходимо будет установить в стандартном местоположении, которое ваша программа знает, как найти (например, что-то вроде /usr/share/progname/database.db в Linux). Во время установки Makefile также необходимо сгенерировать исполняемый файл bash script (или эквивалент), который знает, где находится ваша программа и как ее запустить. То есть, что-то вроде:

#!/bin/sh

exec 'python' '/usr/share/progname/main.py' "[email protected]"

который, очевидно, должен быть установлен как /usr/bin/progname (или что-то еще).

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