【问题标题】:Generally speaking, how are (Python) projects structured?一般来说,(Python)项目是如何构建的?
【发布时间】:2025-12-06 00:05:01
【问题描述】:

在构建我的项目时,我有点迷茫。我尝试以有意义的方式组织事物,但总是每天至少重组整个事物两次。诚然,我的项目不是很大,但我希望不必重组所有内容而只解决一次。

我将描述我当前的程序以尝试理解事物。这是一个带有数据库后端的图形程序,用于计算帆的价格。尚未编写所有内容,但用户将能够从两个下拉菜单中选择风帆类别和型号。根据类别-模型组合,程序将显示复选框和旋转框。这些复选框和旋转框在更改时会从数据库中提取信息,并显示选中该复选框或在旋转框中具有特定数字(例如,以平方米为单位的面积)的价格。

在当前形式下,项目如下所示:

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 保存在 Qt Designer 中制作的所有 .ui 文件。它们是描述 GUI 的 XML 文件。可以使用终端工具将它们转换为 Python 脚本,我已将其嵌入到 generate_gui.py 中。 .qrc 文件也是如此。 generate_gui.py 将自动生成的文件放在 gui 文件夹中,前缀为 ui_ 或后缀为 _rcdatabase.db 目前为空,但最终将用于保存价格和所有内容。

MainWindow.pyOptionsWidget.py 是保存同名对象的 Python 文件,但减去了 .py 后缀。 MainWindow 在其显示表面中包含 OptionsWidget。两个对象都使用它们对应的uirc 文件。

SailQt.pyw 是创建MainWindow 实例的文件,告诉它显示自己,然后告诉 (Py)Qt 进入它的循环并从那里接管。它基本上很像许多图形应用程序的.exe 文件,因为它是一个让程序运行的小文件。

我最初的猜测是将SailQt.pyw 放在sailqt 文件夹中。但随后MainWindow.py 突然需要访问__version__ 字符串。我能弄清楚如何实现这一点的唯一方法是将SailQt.pyw 移动到我项目的根文件夹,并让MainWindow.py 导入sailqt.__version__。但考虑到那是我第 n 次不得不在大多数文件中重新整理并重做行以解决这个微小的混乱,我决定在这里问一下。

我的问题很清楚:

  • 一般来说,Python 项目的结构如何? This pydoc link 很有帮助,但对我来说这更像是一个模块,而不是用户实际执行的东西。
  • 上述结构是否正确?
  • 回答这个问题可以加分,因为它有点跑题了。为什么我可以先做import os然后再做os.system("sudo rm -rf /")之类的东西,但我不能做import sailqt之类的东西然后再做sailqt.gui.generate_gui.generate()

【问题讨论】:

    标签: python qt pyqt structure


    【解决方案1】:

    让我们首先处理您的最后一个问题,因为就构建 python 项目而言,它是最重要的。一旦你弄清楚了如何让导入在你的项目中正常工作,剩下的就变得更容易处理了。

    要了解的关键是当前运行脚本的目录会自动添加到sys.pathstart中。因此,如果您将您的 main.py 脚本(您当前称为 SailQt.pyw在您的包的外部放在*容器目录中,它将保证包导入将始终有效,不无论从哪里执行脚本。

    因此,最小的起始结构可能如下所示:

    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模块,安装/卸载程序等程序运行时需要的资源(如数据库文件) ,需要安装在您的程序知道如何找到的标准位置(例如,Linux 上的/usr/share/progname/database.db 之类的位置)。在安装时,Makefile 还需要生成一个可执行的 bash 脚本(或等效的),它知道您的程序在哪里以及如何启动它。也就是说,类似于:

    #!/bin/sh
    
    exec 'python' '/usr/share/progname/main.py' "$@"
    

    显然需要安装为/usr/bin/progname(或其他)。

    一开始这似乎需要处理很多事情,但当然,找到一个运行良好的项目结构的主要好处是,您可以将其重新用于所有未来的项目(并开始开发自己的项目)用于设置和管理这些项目的模板和工具)。

    【讨论】:

    • 谢谢!这是一个惊人的答案。我已经很快地重新组织了代码,因为我已经完成了一半。项目can be found here。我暂时放弃了makefile,我会在某一天解决这个问题。 (老实说,我还不知道如何制作makefile)。 generate_gui.py 暂时保留了它的一些功能。
    • 测试目录和整体测试怎么样?我很难将使用 pytest 的测试合并到这个结构中。我假设tests 目录应该与packagemain.py 处于同一级别,并且应该使用命令pytesttests 的父目录运行。但是,如何在我的测试脚本中导入例如 app.py(或者更深层次的模块)?
    • @Devlige。使用完全相同的结构。将test.pytests 都放在*project 目录中。这将使它们等同于main.pypackage,并且导入将以完全相同的方式工作。由于test.pymain.py 在同一目录下,因此它可以从testspackage 导入。出于同样的原因,tests 中的所有模块都可以从package 导入(反之亦然,尽管这实际上不是必需的)。
    最近更新 更多