Python中的Scripts目录是反模式吗?如果是,正确的导入方式是什么?
我在每个项目中都会创建一个脚本目录,因为这个目录可以用来放一些不常用的可执行脚本。在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 个回答
一般来说,如果你的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”称为工具可能会更合适。
如果你只有几个工具,并且它们的代码只是连接命令行和你的模块,那么你也可以考虑把它们直接移动到顶层目录。
一些参考资料:
- argparse
- 关于分发包(注意,distutils中使用的
setup.py
脚本也是一个旁边的脚本示例) - 官方Python模块和包教程
- PYTHONPATH
Setuptools可以自动创建脚本,只要你给它提供一个入口点的列表(也就是main()
函数)。如果你已经在使用Setuptools,开启这个功能非常简单。这样你就可以把你的scripts
文件夹和其他的包合并在一起。
个人来说,在脚本目录里放一个 __init__.py
文件让我感觉有点奇怪,但我也能理解在这里(以及在一些开发工具中)它的用处。
不过,如果这些文件已经作为 Python 模块运行,那它们可能就不算是真正的脚本了,不管“真正的脚本”是什么意思(相关内容:这些文件里有 shebang 吗?)。没有具体的上下文很难判断,但也许它们更像是工具模块,属于你整个 Python 代码库的一部分。
在这种情况下,如果在项目级别(你例子中的 project_dir
)添加一个 __init__.py
文件,那么你就可以在你想要的脚本中正常导入其他模块了:
from some_modules_dir import foo
这意味着你最初的 python -m scripts.some_script
是完全合理的……
问题中的链接只提到了运行位于包目录中的脚本,这可能会引发一些问题,因为……嗯……包和脚本是两回事,脚本不是包,包也不是脚本。它们的用途不同,调用方式也不同,所以如果把它们混在一起,最终会搞得一团糟。
其实,Python自己早就有一个Scripts
目录了,大家也没什么意见,所以这并不是一种反模式。
你可以看看如何将可执行文件与库文件分开?,那是我之前工作时处理可执行脚本的方法。到目前为止,我没听说过有什么问题。