UV 工程化项目完整指南
目录
- 预备知识:两种项目类型
- 类型一:工具包(Library)
- 类型二:有入口的应用(Application)
- [pyproject.toml 配置速查表](#pyproject.toml 配置速查表)
- 打包(Build)
- 运行与部署
- 两种类型对比总结
- FAQ
预备知识:两种项目类型
| 类型 | 特点 | 使用方式 | 场景 |
|---|---|---|---|
| 工具包(Library) | 只提供 import,无入口 | from xxx import yyy |
SDK、工具函数库、框架 |
| 应用(Application) | 有 CLI 命令或可 python -m |
终端敲命令 | CLI 工具、Web 服务、脚本入口 |
类型一:工具包(Library)
项目结构
demo_lib/
├── pyproject.toml ← 项目配置
├── README.md
├── .python-version
└── src/
└── greetlib/ ← 包名 = 导入名
├── __init__.py ← 暴露公开 API
├── py.typed ← 类型标注标记
└── greeter.py ← 核心业务模块
各文件内容
pyproject.toml
toml
[project]
name = "greetlib" # pip install / uv add 用的名字
version = "0.1.0" # 语义化版本
description = "A simple greeting library"
readme = "README.md"
requires-python = ">=3.10" # 最低 Python 版本
dependencies = [] # 运行时依赖(自动安装)
[project.optional-dependencies] # pip install greetlib[dev]
dev = ["pytest>=7"]
[project.urls] # PyPI 页面上显示的链接
Homepage = "https://github.com/example/greetlib"
[build-system] # 构建后端(告诉 uv 用什么打包)
requires = ["uv_build>=0.11.15,<0.12.0"]
build-backend = "uv_build"
src/greetlib/init.py
python
"""Public API of greetlib."""
from greetlib.greeter import greet, greet_many
__all__ = ["greet", "greet_many"]
作用:控制 from greetlib import xxx 能拿到什么。
src/greetlib/greeter.py
python
"""Core greeting logic."""
def greet(name: str) -> str:
"""Return a greeting string."""
return f"Hello, {name}!"
def greet_many(names: list[str]) -> list[str]:
"""Greet multiple people."""
return [greet(name) for name in names]
src/greetlib/py.typed
空文件。标记这个包有类型注解。
类型二:有入口的应用(Application)
项目结构
demo_app/
├── pyproject.toml
├── README.md
├── .python-version
└── src/
└── greetapp/
├── __init__.py ← 包标记(可为空)
├── __main__.py ← python -m greetapp 入口
└── cli.py ← CLI 实际逻辑
各文件内容
pyproject.toml
toml
[project]
name = "greetapp"
version = "0.1.0"
description = "A greeting CLI application"
readme = "README.md"
requires-python = ">=3.10"
dependencies = []
[project.optional-dependencies]
dev = ["pytest>=7"]
[project.urls]
Homepage = "https://github.com/example/greetapp"
# ★ 命令行入口,安装后终端直接打 greetapp 就跑
# 格式:命令名 = "模块:函数名"
[project.scripts]
greetapp = "greetapp.cli:main"
[build-system]
requires = ["uv_build>=0.11.15,<0.12.0"]
build-backend = "uv_build"
src/greetapp/init.py
python
"""greetapp package marker."""
空文件,标记该目录为一个 Python 包。
src/greetapp/main.py
python
"""Entry point for `python -m greetapp`."""
from greetapp.cli import main
if __name__ == "__main__":
main()
作用:支持 python -m greetapp 运行。
src/greetapp/cli.py
python
"""CLI entry point."""
def main() -> None:
"""Print a greeting."""
print("Hello from greetapp!")
print("Usage: greetapp <name>")
两种入口方式对比
| 方式 | 配置文件/模块 | 运行命令 |
|---|---|---|
python -m |
同目录下的 __main__.py |
python -m greetapp |
| CLI 命令 | pyproject.toml 的 [project.scripts] |
终端直接打 greetapp |
两者不冲突,可以同时存在。
pyproject.toml 配置速查表
| 配置段 | 作用 | Library | Application |
|---|---|---|---|
[project] |
包名、版本、描述、Python 版本、运行时依赖 | 必填 | 必填 |
[project.scripts] |
定义命令行命令(应用专属) | 不需要 | 按需 |
[project.optional-dependencies] |
可选依赖分组(如 dev/test/docs) | 按需 | 按需 |
[project.urls] |
项目链接(PyPI 页面上显示) | 推荐 | 推荐 |
[build-system] |
声明构建后端,两行固定写法 | 必填 | 必填 |
打包(Build)
bash
cd demo_lib # 进入项目目录
uv build # 生成 .whl + .tar.gz 到 dist/
cd demo_app # 应用项目也一样
uv build
产物在 dist/ 目录下:
dist/
├── greetlib-0.1.0-py3-none-any.whl ← 分发包(主用)
└── greetlib-0.1.0.tar.gz ← 源码包
运行与部署
一、开发时调试(打包前)
在项目目录里直接运行,不需要打包:
bash
uv run greetapp # 走 [project.scripts] 配置
uv run python -m greetapp # 走 __main__.py
uv 会自动管理虚拟环境,无需手动激活。
二、打包后验证(不安装)
打好包后想确认 whl 是否正确,不想污染任何环境:
方法 1 ------ uv run --with(最常用):
bash
# 应用:跑 CLI 命令
uv run --with ./dist/greetapp-0.1.0-py3-none-any.whl greetapp
# 库:验证 import
uv run --with ./dist/greetlib-0.1.0-py3-none-any.whl python -c \
"from greetlib import greet; print(greet('World'))"
uv 自动创建临时虚拟环境安装 whl,跑完就清理。
方法 2 ------ uv tool run --from(语义更清晰,效果一样):
bash
uv tool run --from ./dist/greetapp-0.1.0-py3-none-any.whl greetapp
方法 3 ------ 直接解压 whl(了解原理):
bash
# whl 本质就是 zip 文件
cd /tmp
unzip ../dist/greetapp-0.1.0-py3-none-any.whl -d greetapp_tmp
PYTHONPATH=/tmp/greetapp_tmp python -c "from greetapp.cli import main; main()"
不推荐日常用,只是说明 whl 并非黑盒。
三、正式环境部署运行
生产环境需要稳定、可重复、能自愈。以下是四种常见方案:
方案 A:全局安装 CLI 工具
适用场景:脚本类工具,不依赖具体项目。
bash
# 安装(只需一次)
uv tool install greetapp-0.1.0-py3-none-any.whl
# 之后任何目录下直接执行
greetapp
本质:uv 将 whl 装到其全局工具目录,生成可执行文件到 PATH,与项目依赖隔离。
方案 B:专属虚拟环境(服务/常驻进程)
适用场景:Web 服务、后台任务、需要进程管理的应用。
bash
# 1. 复制 whl 到服务器,创建专属虚拟环境
uv venv /opt/greetapp/.venv
# 2. 把 whl 安装进去
linux安装:/opt/greetapp/.venv/bin/pip install greetapp-0.1.0-py3-none-any.whl
windows安装:uv pip install --python \opt\greetapp\.venv D:\pycharmworkspace\PythonwebProject\dist\testlib-0.1.0-py3-none-any.whl
# 3. 用 systemd 管理进程
windows运行:/opt/greetapp/.venv/Scripts/python -m testlib
linux运行:/opt/greetapp/.venv/bin/python -m testlib
systemd 配置(/etc/systemd/system/greetapp.service):
ini
[Unit]
Description=greetapp service
After=network.target
[Service]
Type=simple
ExecStart=/opt/greetapp/.venv/bin/greetapp
Restart=always
User=greetapp
Group=greetapp
[Install]
WantedBy=multi-user.target
bash
# 启动
systemctl start greetapp
systemctl enable greetapp # 开机自启
如果没有 systemd(如老旧发行版),可用 supervisor:
ini
[program:greetapp]
command=/opt/greetapp/.venv/bin/greetapp
autostart=true
autorestart=true
user=greetapp
方案 C:Docker 容器化(最推荐)
适用场景:任何正式环境,特别是微服务、云原生部署。
dockerfile
FROM python:3.12-slim
COPY greetapp-0.1.0-py3-none-any.whl /app/
RUN pip install /app/greetapp-0.1.0-py3-none-any.whl
CMD ["greetapp"]
bash
# 构建
docker build -t greetapp .
# 运行
docker run greetapp
# 或 docker-compose.yml
yaml
version: "3"
services:
greetapp:
build: .
restart: always
Docker 的好处:环境完全一致、依赖隔离、无需手动管理 venv 和进程。
方案 D:极简环境(无 Docker 无 systemd)
bash
# 创建并使用 venv
python3 -m venv /opt/greetapp/venv
/opt/greetapp/venv/bin/pip install greetapp-0.1.0-py3-none-any.whl
# 写到 crontab 或 rc.local
/opt/greetapp/venv/bin/greetapp
四、让其他项目使用已打好的包
方式 1 ------ 直接装 whl 文件:
bash
uv add ./dist/greetlib-0.1.0-py3-none-any.whl
方式 2 ------ 安装本地项目目录:
bash
uv add /path/to/demo_lib # 只读安装
方式 3 ------ 可编辑安装(改源码即时生效,开发时用):
bash
uv add -e /path/to/demo_lib
方式 4 ------ 发布到 PyPI,全世界都能装:
bash
uv publish
# 别人就能:uv add greetlib
五、运行方式对比总表
| 阶段 | 方式 | 命令 | 适用场景 |
|---|---|---|---|
| 开发调试 | uv run <cmd> |
uv run greetapp |
未打包,在项目目录内 |
| 打包验证 | uv run --with ./xxx.whl <cmd> |
一行命令,临时环境 | 本地测试 whl 是否正确 |
| 生产-小工具 | uv tool install xxx.whl |
全局安装,终端直接调用 | CLI 小工具、脚本 |
| 生产-服务 | 专属 venv + systemd | 进程管理、自愈、日志 | Web 服务、后台任务 |
| 生产-容器 | Docker 镜像 | 环境一致、隔离、可编排 | 微服务、云原生架构 |
六、核心原则
| 环境 | 做法 | 关键点 |
|---|---|---|
| 本地验证 | uv run --with ./xxx.whl <cmd> |
即用即弃,不污染环境 |
| 正式环境 | 先把 whl 装到专属 venv,再跑 entry point | 固定环境、进程管理、日志收集 |
核心区别 :开发/验证时用
--with临时创建环境;正式环境则先安装到独立位置,再用 systemd/Docker 管理进程生命周期。
两种类型对比总结
| 工具包(Library) | 应用(Application) | |
|---|---|---|
有无 __main__.py |
无 | 有 |
有无 [project.scripts] |
无 | 有 |
| 安装方式 | uv add greetlib |
uv tool install greetapp 或 uv add greetapp |
| 主要使用方式 | from greetlib import xxx |
终端敲 greetapp |
| 入口数 | 0 | 1 或多个 |
FAQ
1. __init__.py 能省略吗?
Python 3.3+ 允许没有 __init__.py 的隐式命名空间包(PEP 420),import 也能正常工作。但推荐加上,因为:
- 可以在
__init__.py里放公共导入、版本号、__all__ - 控制包的 API 暴露范围
- 避免意外把无关目录合并成命名空间包
2. __init__.py 和 __main__.py 能不能只有一个?
__init__.py |
__main__.py |
结果 |
|---|---|---|
| ✅ | ❌ | 可 import,不能 python -m |
| ❌ | ✅ | 可 python -m,但 import 行为不确定(隐式命名空间包) |
| ✅ | ✅ | 两者皆可 ✅ |
3. uv init --lib 和 uv init --app 有什么区别?
| 参数 | 生成的结构 | 自动添加 |
|---|---|---|
--lib |
src 布局 + src/包名/__init__.py |
py.typed 类型标记文件 |
--app |
平级布局 + main.py |
无额外配置 |
实际开发中,推荐无论 lib 还是 app 都用 src 布局 (--lib 的结构更规范),app 项目手动创建 __main__.py 和 [project.scripts]。
4. 什么情况用 uv run 什么情况用 uv add?
| 场景 | 命令 |
|---|---|
| 临时跑一次脚本,不想污染环境 | uv run python script.py |
| 安装依赖到当前项目 | uv add requests |
| 全局安装一个 CLI 工具 | uv tool install greetapp |
| 在别的项目里引用一个本地包 | uv add /path/to/mylib |