文章目录
- 为什么选 uv,而不是再手搓 venv
- 目录与模块边界:函数优先,CLI 另放
- 跨平台:能统一就统一,不能统一就拆文件
- 项目根怎么找:只认 pyproject.toml
- 资源释放与中断:退出即清空占用
- 小结:可复制的检查清单
自己维护一堆零散脚本时,最难受的往往不是「写不出来」,而是:依赖装乱、Win/macOS 行为不一致、业务逻辑和 argparse 缠在一起、跑完留下孤儿进程。本文以一套真实的跨平台 Python CLI 工具库(mscc-cli)为例,整理可复用的工程约定。
为什么选 uv,而不是再手搓 venv
mscc-cli 用pyproject.toml声明项目与依赖,日常在仓库根执行:
uvsyncuv run ai/cli.py--prompt"短任务"uv run会按当前锁文件起隔离环境再跑脚本,团队成员少争论「你本机 Python 是哪一版」。项目要求requires-python >= 3.13,依赖写在[project].dependencies,开发依赖放[dependency-groups].dev。核心可复用包(如core)用 hatchling 打进 wheel,业务脚本直接from core...导入。
要点:uv run是快捷测试入口,不是业务主路径。长文本、多行任务应走可 import 的函数,避免 Windows 上命令行参数被截断。
目录与模块边界:函数优先,CLI 另放
约定可以压成一句话:能力做成函数,命令行只做薄壳。
| 文件 | 职责 |
|---|---|
xxx.py/workflow.py | 可 import 的业务函数 |
cli.py | 仅 argparse、校验、退出码,再调用函数 |
例如 Agent 入口:业务用from ai.agent import run_agent;人工试跑用:
uv run ai/cli.py--prompt"用一句话总结本仓库"多步编排同理:workflow.py不含argparse、不含__main__;cli.py不复制编排逻辑。其它脚本只调主入口,不直接 import 某一平台实现文件。
跨平台:能统一就统一,不能统一就拆文件
跨平台优先pathlib、sys.platform,子进程用参数列表,禁止shell=True。同一套代码拧不动时,不要在一个文件里堆if win / else:
- 实现拆成
*_win.py/*_mac.py(必要时*_linux.py) - 由
workflow.py或主入口按平台选用 cli.py不感知平台细节
状态输出统一用[OK]/[SKIP]/[FAIL]/[ERROR],失败非零退出,方便脚本串联与日志检索。
项目根怎么找:只认 pyproject.toml
硬编码parent.parent、或拿.git/ 文档文件当根标记,换目录就碎。mscc-cli 的做法是自脚本路径或 cwd 向上找,第一个带pyproject.toml的目录即根:
defrepo_root(start:Path|None=None)->Path:here=(startorPath.cwd()).resolve()ifhere.is_file():here=here.parentforcandidatein(here,*here.parents):if(candidate/"pyproject.toml").is_file():returncandidatereturnPath.cwd().resolve()ensure_repo_path再把根目录插入sys.path,保证任意子目录脚本都能稳定from core.logger import Logger。中间产物放build/,日志用统一Logger写到logs/<日期>_run.log,不要各脚本自建一套。
资源释放与中断:退出即清空占用
浏览器、子进程、文件锁、HTTP session,只要脚本进程要退出(成功、失败、超时、Ctrl+C),都必须收尾。实践上:
- 用
with/try/finally,禁止只在成功分支里close finally里清理要防二次异常,且幂等- Ctrl+C 视为正常中止:打印
[SKIP] 已中断,退出码130,并杀掉本脚本拉起的子进程
浏览器自动化统一走共用封装(如core.browser.open_browser),按--user-data隔离 profile,退出关闭 context / Playwright,避免残留 Chrome。
小结:可复制的检查清单
- 依赖与运行交给 uv +
pyproject.toml - 业务函数可 import;
cli.py只做参数与退出码 - 平台差异拆文件,主入口统一调度
- 根目录只认
pyproject.toml;产物进build/,日志统一 - 任何退出路径都释放占用
按这几条搭工具库,脚本会从「能跑」变成「能复用、能协作、能在三端稳定跑」。后续加新能力时,先问自己:别人能不能import调用?uv run 模块/cli.py能不能一分钟验通?答得上,结构就对了。