2305 字
12 分钟
使用uv开发一个Python项目

我在此对这篇教程进行一下补充,这部分内容其实只适用于单文件项目。而在编写当初的项目时,单文件模式其实就已经开始难以管理了,main.py 看起来非常的乱,完全不知道什么函数在哪里。我之后又使用 Typer 重构了项目,分离了工具函数与命令函数,并令其拥有了用户级的配置文件,在未来还会将业务逻辑与命令函数分离,具体的做法可以看我的项目,以及这篇教程

lancersoul
/
lmp_anly
Waiting for api.github.com...
00K
0K
0K
Waiting...

随着我对LAMMPS数据后处理要求和后处理项目的增多,之前使用的Python脚本有点难以管理(每次都要从其他项目中复制脚本并修改输出文件名有点蠢)。将我之前的Python脚本功能集合开发成一个Python CLI应用的必要性越来越大。由于我是一个半路出家的和尚(不管从Python还是MD来说),我是通过Google Gemini学习的Python项目开发方式。为了防止遗忘,同时也为了强化记忆,我将从Google Gemini问来的如何使用uv进行项目管理的方式记载此处,同时剔除了AI幻觉便于学习。

uv是一个用 Rust 编写的极速 Python 包和项目管理工具,它使用 pyproject.toml 来组织Python项目。项目开发 | uv 中文文档描述了使用uv来进行Python项目开发的相应功能,我将叙述一个使用uv开发CLI应用的流程。

使用uv来初始化和管理项目#

创建新项目#

使用 uv init 来创建一个新的Python项目:

Terminal window
uv init my-cli-project
cd my-cli-project

也可以在当前已经开发中的项目中使用uv来重新组织,在当前的工作目录中初始化一个项目:

Terminal window
mkdir my-cli-project && cd $_
uv init

上述命令会在项目目录中直接创建以下文件:

.
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

项目结构#

这里uv默认构建的Python项目是一个平面的脚本项目, main.py 文件直接放在了主目录下。要开发一个CLI应用,你应该将项目调整为以下结构,Gemini推荐使用 src 的结构布局:

my-cli-project/
├── .venv/ # 虚拟环境 (uv venv 创建, 应被Git忽略)
|
├── src/ # 源代码的根目录
│ └── my_cli_project/ # 这才是你实际的Python包 (包名通常用下划线)
│ ├── __init__.py # 包初始化文件, 可以为空
│ └── main.py # CLI入口和核心逻辑
├── tests/ # 存放自动化测试代码的目录,可以没有
│ ├── __init__.py # 标记为包
│ └── test_main.py # 针对 main.py 的测试文件
├── .gitignore # Git忽略文件配置
├── pyproject.toml # 项目配置文件
└── README.md # 项目说明文档

每个部分的职责#

  • my_cli_project/ (项目根目录)

    • 这是你项目的“容器”,所有与项目相关的文件都在这里。你的版本控制(如Git)也应该在这里初始化。
  • .venv/

    • uv venv 创建的虚拟环境目录。关键:一定要将它添加到 .gitignore 文件中,永远不要提交到版本控制里。
  • src/ (源代码目录)

    • 这是一个专门存放你可安装源代码的目录。这是src布局的核心。它的存在是为了将你的源代码与其他项目文件(如测试、文档、配置文件)清晰地隔离开。
  • src/my_cli_project/ (Python包目录)

    • 这才是你真正的Python包。当你(或其他人)通过pipuv安装你的项目时,只有这个目录里的内容会被安装到Python的site-packages中。

    • 它的名字 (my_cli_project) 就是包名,也是你在代码中 import 时使用的名字。

    • __init__.py: 一个空文件,用于告诉Python这个目录是一个包。

    • main.py: 你的CLI应用主文件,里面包含 Typerargparse 的代码。随着项目变大,你可以在这里添加更多的 .py 文件,如 utils.py, core.py 等。

  • tests/

    • 存放所有测试代码的地方。使用像 pytest 这样的框架时,它会自动发现并运行这个目录下的测试。将测试与源代码分开是一种非常好的实践。

    • 如果不使用 pytest 这样的测试工具,或你的项目比较小的时候,可以在 main.py 文件的 if __name__ == "__main__": 中测试你的函数。

  • .gitignore

    • 告诉Git哪些文件或目录不应该被追踪。uv会自动将其自动生成的结构中不应该被Git所控制的内容包含在 .gitignore 中,你只需要关心自己创建的文件。
  • pyproject.toml

    • 项目的中央配置文件。它定义了项目名称、版本、依赖、构建系统以及我们为CLI设置的 [project.scripts] 入口点。
  • README.md

    • 项目的门面。用Markdown格式编写,介绍你的项目是做什么的、如何安装、如何使用。

正式开始开发#

main.py 文件的结构#

开发CLI应用需要使用 argpraseTyper 这样的命令行解析工具。 Typer 是一个第三方模块,它是一个用于构建CLI的常用框架,并且依赖 rich 库,但由于我的CLI应用有点简单,我选择了使用内置的 argparse 这个内置模块。我所开发的 main.py 结构如下所示:

import module
# 实现功能的函数
def do_hello(name,flag):
if flag:
print("Hello World!")
else if:
print(f"Hello World! from {name}")
# 主函数
def main():
# 1. 创建主解析器
parser = argparse.ArgumentParser(description="A cool CLI application built with argparse.")
# 2. 创建子命令解析器
# 这是实现 git <command> 这种模式的关键
subparsers = parser.add_subparsers(dest="command", help="Available commands", required=True)
# 3. 创建 'hello' 命令的解析器
parser_greet = subparsers.add_parser("hello", help="Greets someone.")
parser_greet.add_argument(
"--name", # 传参的全称, 实现类似功能: my-cli hello --name lancer_soul
"-n", # 传参的简称,实现类似功能: my-cli hello -n laner_soul
type=str, # 规定传参的类型,默认为str,其他类型需要特殊指定
default="World", # 规定传参默认值
help="The name to greet."
)
parser_greet.add_argument(
"-ignore", "-i", action="store_true", # 代表-i是一个flag,指定该选项为True, 不指定为False, 传入值时会报错
help="ignore --name to print 'Hello World!'"
)
# 5. 解析来自终端的参数
args = parser.parse_args()
# 6. 根据解析出的命令,调用对应的函数,如果不传入命令,argparse会默认显示帮助
if args.command == "greet":
do_hello(name=args.name, flag=args.ignore)
if __name__ == "__main__":
main()

配置 pyproject.toml#

上述文件其实就是平常所使用的脚本文件,但要构建CLI应用,我们还需要配置 pyproject.toml 文件,除uv初始自动生成的内容外和 uv add module 生成的 dependencies 外,我们还需要填入以下内容:

[build-system] # 可以没有,不指定就默认使用setputools
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project.scripts]
my-cli = "my_cli_project.main:main" # 定义CLI入口,my-cli会使用my_cli_project包main.py中的main函数

uv能够自动解析依赖并安装,我们不需要手动 uv add ,直接 uv run main.py 就可以安装文件中声明的依赖。但使用 uv add module 手动安装依赖并进入venv可以让nvim lsp提供补全。

测试应用#

当你完成上述编辑与配置后,我们可以开始测试这个应用,测试有两种方式:

  • 一种是在当前的虚拟环境中以“可编辑模式”安装这个应用并测试,可编辑模式意味着你任何对源码的修改都会立即生效,非常适合开发环境。

  • 另一种是全新安装软件包并测试,这意味着你与其他用户的环境一致,用于最后测试。

可编辑模式#

在你的项目根目录下,运行以下代码在当前虚拟环境以可编辑模式安装应用:

Terminal window
uv pip install -e .

接下来测试你的应用:

Terminal window
my-cli hello -n lancer_soul

全新安装#

全新安装应用使用了uv提供的 uvx 命令,该命令会临时创建一个虚拟环境并安装指定的包,结束后立即销毁创建的环境。

Terminal window
uvx --from . my-cli hello -n lancer-soul

安装自己的应用#

只为你自己安装应用#

我不想把我的应用提交到PyPI,并且我也不想在我的LAMMPS环境中安装venv,因此我选择将我的包安装为uv的一个tool。只需要在项目根目录的虚拟环境外运行:

Terminal window
uv tool install --from .

这个命令会为你的包创建一个全新的虚拟环境并将其安装到环境中,之后你在任何地方只需要运行以下命令就查看已安装的包并使用CLI应用:

Terminal window
uv tool list # 查看目前已安装的包
uv tool run my-cli hello -n lancer_soul # 运行你的包

将应用提交到PyPI并使用#

在将应用提交到PyPI前你需要先构建自己的应用,uv的构建目录默认为dist/

Terminal window
uv build

发布包的命令为:

Terminal window
uv publish

通过 --tokenUV_PUBLISH_TOKEN 设置PyPI令牌来设置自己的账号发布你的包。

结语#

到此,你已经成功开发并安装了一个自己开发的Python应用。恭喜!

使用uv开发一个Python项目
https://blog.lancersoul.top/posts/uv-project-dev/
作者
Lancer Soul
发布于
2025-07-06
许可协议
CC BY-NC-SA 4.0