Python 项目的程序入口与脚本管理
在采用现代化的 src
布局后,一个常见的问题是:"我应该如何运行我的代码?程序的入口在哪里?" 这个问题触及了 Python 与 JavaScript 在项目执行理念上的一个核心差异。
参考资料:
- How to structure a python project with multiple entry points - By Claude
- Python Apps the Right Way: entry points and scripts - By Chris Warrick
1. 核心理念:从"运行文件"到"运行包"
- JavaScript (Node.js): 通常有一个明确的入口文件(如
index.js
或main.js
),你可以通过node index.js
来启动整个应用。 - Python: 现代 Python 鼓励将你的项目视为一个可安装的包。直接运行包内的某个文件(如
python src/my_package/app.py
)是一种反模式,因为它会破坏 Python 的导入系统,导致ImportError
。
正确的做法是,首先以可编辑模式安装你的包,然后通过模块或定义的入口点来运行它。
# 在你的项目根目录 (包含 pyproject.toml 的地方)
# 这会将你的项目链接到当前的虚拟环境中,让解释器能找到它
pip install -e .
安装之后,你就有两种"Pythonic"的方式来运行你的代码。
2. 定义程序入口 (Entry Points)
方式一:使用模块执行器 (python -m
)
python -m <module_name>
命令告诉 Python 解释器去作为模块查找并执行 module_name
,而不是作为普通脚本。这会自动处理好路径问题,让包内的相对导入正常工作。
假设你的入口逻辑在 src/my_package/app.py
文件中,并被 if __name__ == "__main__"
保护:
# src/my_package/app.py
from . import helper # 这是一个有效的相对导入
def main():
print("应用主逻辑开始...")
helper.do_something()
if __name__ == "__main__":
main()
在安装了包之后,你可以这样运行它:
python -m my_package.app
这会正确地执行 app.py
中的代码块,并且 from . import helper
也能正常工作。
方式二(推荐):使用 [project.scripts]
这是最现代、最强大的方式,它能让你的 Python 应用像一个真正的原生命令行工具一样被调用。你可以在 pyproject.toml
文件中定义一个或多个"脚本入口"。
1. 准备你的函数 首先,确保你的入口逻辑被封装在一个无参数的函数中,例如上面例子中的 main()
函数。
2. 在 pyproject.toml
中配置 在 [project]
表下,添加一个新的 scripts
表:
# pyproject.toml
[project]
name = "my-awesome-app"
# ... 其他元数据 ...
[project.scripts]
my-awesome-app = "my_package.app:main"
another-tool = "my_package.tools:run_tool"
- 格式:
命令名称 = "包名.模块名:函数名"
my-awesome-app
: 这是用户安装你的包后,可以在终端直接输入的命令。"my_package.app:main"
: 这指向了当用户运行该命令时,实际应该被调用的函数。
3. 重新安装 每次修改 [project.scripts]
后,你需要重新运行 pip install -e .
来让这些脚本生效。
4. 运行 现在,你或你的用户可以在终端的任何地方直接运行:
my-awesome-app
这会直接调用 my_package.app
模块里的 main
函数。这种方式不仅方便,而且完全隐藏了项目的内部文件结构,提供了非常专业的命令行体验。
3. 类似 npm scripts
的任务运行器
[project.scripts]
解决了"程序入口"的问题,但对于开发过程中的其他任务(如测试、linting、构建),Python 社区并没有一个像 npm scripts
那样内置于核心配置文件中的统一标准。不过,有几种非常流行的替代方案:
Makefile
(通用且简单)
Makefile
是一个非常强大的通用任务运行器,不限于任何语言。你可以用它来定义一系列开发任务的快捷方式。
# Makefile
.PHONY: install test lint run
# 安装所有依赖,包括开发依赖
install:
pip install -e ".[dev]"
# 运行测试
test:
pytest
# 运行 linter
lint:
ruff check .
# 通过入口点运行主程序
run:
my-awesome-app
开发者只需要运行 make test
, make run
等简单命令即可。
现代构建工具的脚本功能
一些现代的 Python 项目管理工具正在向 npm scripts
的体验靠拢,它们允许你在 pyproject.toml
的特定 [tool.*.scripts]
表中定义任务。
Poetry 示例:
# pyproject.toml (using Poetry)
[tool.poetry.scripts]
test = "pytest"
lint = "ruff check ."
run = "my-awesome-app"
然后你可以通过 poetry run test
, poetry run lint
来执行这些任务。
Hatch 示例:
# pyproject.toml (using Hatch)
[tool.hatch.scripts]
test = "pytest"
lint = "ruff check ."
run = "my-awesome-app"
然后你可以通过 hatch run test
, hatch run lint
来执行。
结论: 虽然 Python 没有与 npm scripts
完全一一对应的原生机制,但通过结合 [project.scripts]
(用于程序入口) 和任务运行器 (如 Makefile
或工具内置脚本),你可以构建出一个同样强大、清晰且易于维护的开发工作流。