一般来说,(Python)项目是如何构建的?

时间:2014-03-04 16:49:34

标签: python qt pyqt structure

在构建我的项目时,我有点迷失。我尝试以有意义的方式构建事物,但总是每天至少两次重组整个事情。当然,我的项目不是很大,但我希望不必重新构建所有内容,只需坚持一次。

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

目前的形式,项目如下:

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()

1 个答案:

答案 0 :(得分:23)

让我们先处理你的上一个问题,因为就构建python项目而言,它是最重要的。一旦你整理出如何在项目中使导入正常工作,其余部分就会变得更容易处理。

要理解的关键是当前运行的脚本的目录会自动添加到sys.path start 中。因此,如果您将包的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

并且可以从包的任何位置使用相同形式的完全限定的import语句。因此,例如,这个稍微复杂的结构:

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(或其他)。

一开始可能看起来有很多事情要处理,但当然找到一个效果很好的项目结构的主要好处是,你可以将它重新用于所有未来的项目(并开始开发自己的项目)用于设置和管理这些项目的模板和工具。)