Python中的Scripts目录是反模式吗?如果是,正确的导入方式是什么?

16 投票
4 回答
986 浏览
提问于 2025-05-01 10:15

我在每个项目中都会创建一个脚本目录,因为这个目录可以用来放一些不常用的可执行脚本。在Python中,我总是会在我的脚本目录里放一个__init__.py文件,这样我就可以把脚本当成包来运行(比如用python -m scripts.some_scripts),还可以从兄弟目录中加载模块。不过,根据我查阅的资料和一些搜索,我开始觉得这样做可能不是个好习惯。

那么,假设有这样的结构:

project_dir/
    some_modules_dir/
        foo.py
        bar.py
        ...
    scripts/
        some_script.py
        other_script.py
        ...

那么,运行脚本的正确方式是什么?如何让它们从兄弟目录some_modules_dir中导入呢?哪些目录应该包含__init__.py,哪些不应该?我希望尽量遵循PEP8规范,并且希望能简化运行脚本的过程。如果说根本不应该有脚本目录,那你们通常是怎么做的呢?

暂无标签

4 个回答

1

一般来说,如果你的Python项目里面有多个属于这个项目的包,那么应该有一个顶层包,其他的包都是它的子包。因为它们都是这个项目的一部分,所以应该有一个共同的理由让它们存在。一个目录只有在里面有一个__init__.py文件时,才算是一个包。换句话说,如果你项目里的两个兄弟目录都有__init__.py,那么它们的父目录也应该有一个。因此,如果你有充分的理由在项目根目录下直接放一个“scripts”包和一个“modules”包,那么你的项目根目录也应该是一个包。

如果你的顶层包不是项目根目录,通常可以在顶层包旁边放一些“松散”的Python脚本。像这样:

project_root/
    top_level_package/
        __init__.py
        module.py
        subpackage/
            __init__.py
            anothermodule.py
    adjacent_script.py
    adjacent_script_2.py

这些“松散”的脚本可以直接从包和模块中导入,因为它们和顶层包在同一个目录下。这样的结构假设顶层包包含了你项目中所有“有趣”的代码(也就是你项目的“卖点”或“核心”),而这些“松散”的脚本只是作为你访问顶层包中特定功能的入口点。例如,你可能会有一个脚本用来启动测试套件、安装软件,或者如果你的项目是一个应用程序的话,用来启动应用程序。

关于你提到的特定模式,我觉得你把“scripts”和“modules”区分开来,是因为这些脚本是你和模块之间的接口。如果这些脚本只是供你作为开发者使用,并且你使用得不频繁,那么只要根目录有__init__.py,继续使用你的方式是可以的。不过,如果你(或者其他人)也把这些“scripts”当作最终用户来使用,或者你使用得比较频繁,那么在顶层包旁边提供一个脚本,把“scripts”目录里的功能整合成一个命令(或者几个命令,如果它们的功能差异很大),会显得更整洁。你可能想用argparse这个标准模块来处理这个旁边的脚本。在这两种情况下,把“scripts”称为工具可能会更合适。

如果你只有几个工具,并且它们的代码只是连接命令行和你的模块,那么你也可以考虑把它们直接移动到顶层目录。


一些参考资料:

3

Setuptools可以自动创建脚本,只要你给它提供一个入口点的列表(也就是main()函数)。如果你已经在使用Setuptools,开启这个功能非常简单。这样你就可以把你的scripts文件夹和其他的包合并在一起。

3

个人来说,在脚本目录里放一个 __init__.py 文件让我感觉有点奇怪,但我也能理解在这里(以及在一些开发工具中)它的用处。

不过,如果这些文件已经作为 Python 模块运行,那它们可能就不算是真正的脚本了,不管“真正的脚本”是什么意思(相关内容:这些文件里有 shebang 吗?)。没有具体的上下文很难判断,但也许它们更像是工具模块,属于你整个 Python 代码库的一部分。

在这种情况下,如果在项目级别(你例子中的 project_dir)添加一个 __init__.py 文件,那么你就可以在你想要的脚本中正常导入其他模块了:

from some_modules_dir import foo

这意味着你最初的 python -m scripts.some_script 是完全合理的……

6

问题中的链接只提到了运行位于包目录中的脚本,这可能会引发一些问题,因为……嗯……包和脚本是两回事,脚本不是包,包也不是脚本。它们的用途不同,调用方式也不同,所以如果把它们混在一起,最终会搞得一团糟。

其实,Python自己早就有一个Scripts目录了,大家也没什么意见,所以这并不是一种反模式。

你可以看看如何将可执行文件与库文件分开?,那是我之前工作时处理可执行脚本的方法。到目前为止,我没听说过有什么问题。

撰写回答