用 uv 来运行 Python 脚本

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

https://docs.astral.sh/uv/guides/scripts/

1. MECE 原则拆解

为了系统性地理解 uv 在脚本运行方面的功能,我们可以将文章内容拆解为以下几个相互独立、完全穷尽的模块:

  1. 核心问题 (The “Why”): uv 旨在解决什么传统痛点?
  2. 核心理念 (The “What”): uv 提出的解决方案是什么?
  3. 功能实现 (The “How”): uv 是如何通过具体功能实现其理念的?
    • A. 脚本执行: 如何运行不同类型的脚本。
    • B. 依赖管理: 如何声明和管理脚本的依赖。
    • C. 环境管理: 如何处理 Python 版本和包索引。
    • D. 可复现性: 如何保证脚本在不同时间、不同环境下行为一致。
    • E. 集成与分发: 如何让脚本更像一个独立的可执行程序。
  4. 本质思考: uv 这种模式的本质是什么?
  5. 实践建议: 下一步可以做什么?

2. 逐一解析

1. 核心问题:传统 Python 脚本的痛点

在没有 uv 之前,运行一个带依赖的 Python 脚本通常很繁琐:

  • 手动环境管理: 你需要手动创建虚拟环境 (python -m venv .venv),激活它 (source .venv/bin/activate),然后安装依赖 (pip install -r requirements.txt),最后才能运行脚本 (python script.py)。
  • 依赖与代码分离: 依赖项通常写在 requirements.txt 文件里,与脚本本身是分离的,分发和管理不便。
  • 隐式依赖: 一个项目中的 requirements.txt 可能包含整个项目的所有依赖,而你的脚本可能只需要其中一小部分,造成了环境臃肿。
2. 核心理念:脚本即环境,声明式运行

uv 的核心理念是 “将环境管理和脚本运行合二为一”

它推崇一种声明式的方法:让脚本文件自身就包含运行它所需要的所有信息(如依赖包、Python 版本)。当你使用 uv run 时,uv 会在背后为你完成所有繁琐工作:

  1. 读取脚本中的元数据(或命令行参数)。
  2. 在缓存中快速创建一个隔离的、临时的虚拟环境。
  3. 安装声明的依赖。
  4. 执行脚本。
  5. 执行完毕,这个临时环境几乎没有存在感。

这个过程是原子化的,极大地简化了工作流。

3. 功能实现:uv 的“工具箱”
A. 脚本执行 (Running Scripts)
  • 无依赖脚本: 最简单的场景,直接运行。
    uv run simple_script.py
    
  • 带参数脚本: 和 python 命令一样传递参数。
    uv run script_with_args.py "hello world"
    
  • 从标准输入读取: 可以通过管道将代码传给 uv
    echo 'print("hello from stdin")' | uv run -
    
  • 与项目环境隔离: 如果你在一个有 pyproject.toml 的项目目录中,uv 默认会安装项目依赖。使用 --no-project 可以忽略项目,只为当前脚本创建纯净环境。
    # 即使在项目目录里,也只为 my_script.py 创建独立环境
    uv run --no-project my_script.py
    
B. 依赖管理 (Dependency Management)

这是 uv 最强大的功能之一。它提供了两种声明依赖的方式:

  1. 临时指定 (Ad-hoc): 使用 --with 参数,适合一次性运行或测试。

    # 临时为脚本安装 rich 和 requests 包
    uv run --with rich --with "requests<3" my_script.py
    
  2. 内联元数据 (Inline Metadata - 推荐): 遵循 PEP 723 规范,将依赖直接写在 Python 脚本的注释里。uv 提供了命令来帮你管理这个元数据块。

    • 初始化脚本:
      uv init --script my_script.py
      
    • 添加/更新依赖:
      uv add --script my_script.py rich "requests<3"
      

    这会在 my_script.py 的顶部生成或更新一个 /// script 块:

    # /// script
    # dependencies = [
    #   "rich",
    #   "requests<3",
    # ]
    # ///
    import requests
    from rich import print
    
    # ... 你的代码 ...
    

    当你用 uv run my_script.py 运行时,uv 会自动读取这个块并安装依赖。这是 uv 实现“脚本即环境”理念的关键。

C. 环境管理 (Environment Management)
  • Python 版本: 你可以在元数据中或命令行中指定 Python 版本,uv 会自动寻找或下载安装。
    • 元数据中:
      # /// script
      # requires-python = ">=3.10"
      # dependencies = []
      # ///
      
    • 命令行中:
      uv run --python 3.11 my_script.py
      
  • 自定义包索引: 如果你使用私有 PyPI 仓库,可以通过 --index 指定。
    uv add --script --index "https://private.repo/simple" my_script.py my-private-package
    
D. 可复现性 (Reproducibility)
  • 依赖锁定 (Locking): 为了确保每次运行使用的依赖版本完全一致,可以为脚本创建一个锁文件。
    # 这会生成一个 my_script.py.lock 文件
    uv lock --script my_script.py
    
    之后 uv run 会优先使用这个锁文件,确保了依赖的确定性。
  • 时间旅行 (Time-based Reproducibility): 这是一个非常精妙的功能。你可以通过 exclude-newer 字段,告诉 uv 忽略某个时间点之后发布的包版本,这能极大地防止未来某个依赖的更新破坏你的脚本。
    # /// script
    # dependencies = ["requests"]
    # [tool.uv]
    # exclude-newer = "2023-10-16T00:00:00Z" # 忽略此日期后发布的 requests 版本
    # ///
    
E. 集成与分发 (Integration & Distribution)
  • 创建可执行脚本 (Shebang): 你可以在脚本顶部使用 shebang,结合 uv run,让你的 Python 脚本变成一个可以直接在 Shell 中运行的命令。
    #!/usr/bin/env -S uv run --script
    # /// script
    # dependencies = ["rich"]
    # ///
    from rich import print
    print("Hello, [bold green]executable[/bold green] world!")
    
    保存为 greet,然后 chmod +x greet,之后就可以直接 ./greet 运行了。这对于编写命令行工具非常有用。

4. 本质思考:这玩意儿的本质是什么?

uv 在脚本运行方面的本质是 “为 Python 脚本提供了类似 Docker 的运行时体验”

我们可以做一个类比:

Dockeruv for Scripts描述
Dockerfile内联元数据 /// script声明式定义:定义运行所需的环境和依赖。
docker builduv lock / 第一次 uv run环境构建:解析依赖,创建并缓存一个确定的环境。
docker runuv run原子化执行:在一个命令中完成环境准备和代码执行。
Docker Imageuv 的全局缓存不可变和可缓存:构建好的环境被缓存,下次运行极快。

uv 将 Python 脚本从一个单纯的“代码文件”提升为了一个“自包含、可执行的软件单元”。它抽象掉了虚拟环境的底层细节,让开发者更专注于代码逻辑本身。


5. 实践建议:下一步做什么?

  1. 安装 uv: 如果还没安装,立刻去安装它。
  2. 改造旧脚本: 找一个你以前写的、带 requirements.txt 的小项目或脚本。
    • 尝试用 uv run --with <package> 运行它。
    • 然后使用 uv add --script 将依赖内联到脚本中,并删除 requirements.txt。感受一下代码和依赖在一起的清爽感。
  3. 编写一个新工具: 写一个新的命令行小工具,比如一个能查询天气或处理文本的脚本。
    • uv init --script 开始。
    • 使用 shebang (#!/usr/bin/env -S uv run --script) 让它变成可执行文件。
    • 把它放到你的 PATH 路径下,体验像原生命令一样使用它的感觉。
  4. 探索可复现性: 对一个脚本使用 uv lock,看看生成的 .lock 文件。然后尝试修改脚本中的依赖声明,再次运行 uv run,观察 uv 是如何提示你锁文件过期的。这能加深对依赖确定性的理解。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值