开发者

python之uv使用详解

目录
  • 安装与更新
    • standalone
    • pip 安装
  • 创建php以及初始化项目
    • 依赖管理
      • uv run
        • 直接在命令行运行python编程客栈代码片段
        • 直接运行项目中可执行脚本文件
        • 运行python包中快捷指令
      • uv项目本地运行调试细节
        • vscode 中运行调试uv项目
        • 命令行运行
      • 深入理解 uv lock, uv sync, uv lock
        • uv lock 行为解析
        • uv sync 行为解析
        • uv run 行为解析
      • uv项目的docker file
        • 总结

          uv:

          conda create -n xxx python=3.11 -yconda activate xxxpip install -r requirements.txt

          安装与更新

          这里只讲两种笔者认为最多涉及到的安装与更新方式,分别是命令行standard安装更新和pip安装更新

          standalone

          本地开发机器上安装以及更新的方式,笔者是Ubuntu系统,所以安装使用如下指令

          curl -LsSf https://astral.sh/uv/install.sh | sh
          

          参考官网uv安装

          更新的指令即是:

          UV_NO_MODIFY_PATH=1 uv self update
          

          pip 安装

          pip 安装适合在构建docker镜像的时候选择的安装方式,因为一般构建docker镜像选择的基础镜像都是一个最小化的python basic image.

          它可能只包含基本的python环境和pip,所以在Dockerfile中使用pip安装uv,安装指令如下:

          pip install uv
          

          既然是pip安装的包,更新指令就是

          pip install --upgrade uv
          

          不过这个更新指令基本使用不到,构建镜像过程中只需要在基础镜像安装uv,不需要更新uv.

          创建以及初始化项目

          关键指令为

          uv init [OPTIONS] [PATH]
          

          比如在python-uv目录下执行uv init --python=3.11 test命令会创建一个名字为test的子目录且子目录作为项目根目录,命令指定了项目运行所需的python版本为3.11,同时在test目录下创建项目配置文件pyproject.toml,配置文件中设置项目名称为test,和项目根目录同名,同时还有一些git repo必需的文件以及文件夹,可见init指令也将项目初始化成一个git repo.先看一下目录结构:

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

          如果init指令不指定参数PATH则是将运行uv init所在目录作为项目根目录去创建上面这些内容.

          此时在项目内运行uv sync才能看到虚拟环境目录.venvuv.lock被创建.

          所以完整的项目初始化之后结构如下:

          └── test
              ├── .git
              ├── .gitignore
              ├── main.py
              ├── pyproject.toml
              ├── .python-version
              ├── README.md
              ├── uv.lock
              └── .venv
          

          有几个重要文件和文件夹需要提前说明:

          • .venv: 项目python虚拟环境和依赖包相关文件夹. 可以在项目目录上下文执行指令source .venv/bin/activate激活虚拟环境.但是由于使用uv来构建以及管理的项目使用uv指令居多,所以一般情况下不需要显式的激活这个虚拟环境.同时.venv也不应该上传到版本控制系统,每一次项目repo同步下来只需要运行uv sync则可以更新本地的虚拟环境中lib更新到最新,继续开发项目或者本地调试.
          • pyproject.toml: 存放整个项目的元数据,视作项目配置文件,其内部包含项目名称,描述,依赖,构建,脚本以及工具等等,一般执行uv相关命令都会涉及到更改此配置文件,官方关于此配置文件编写教程链接pyproject.如果从远端同步下来uv项目,可以执行uv sync即是根据此配置文件去构建本地的开发虚拟环境和依赖包.
          • uv.lock 文件:项目依赖的精确版本信息,和pyproject.toml配置不同,配置文件一般是描述依赖的版本边界约束,比如某个依赖包最低版本或者最高版本.lock文件中是真正虚拟环境内安装的精确版本,任何涉及到更新依赖的命令比如uv syncuv add {package}等操作在依赖的版本边界约束内可能拉取最新的版本,就会更新此lock文件.此文件真正起作用的地方是在CI构建过程,因为虚拟环境文件不会提交到版本管理系统,每次构建都需要拉取这些依赖,涉及到使用指令uv sync.由于前面所说这个指令可能会拉取新的依赖版本可能引入不兼容问题,因此使用指令uv sync --frozen强制使用uv.lock文件中的精确版本来完成构建过程,保证构建前后的版本一致,降低构建过程引入新版本带来的风险.uv.lock文件要求必须上传到版本控制系统且不可以手动更改.

          这里可以看到init甚至可以将项目初始化成一个git repo,实际可能不需要这个功能,多半情况下本地已经有git repo了只需要创建以及初始化uv 项目即可,这个情况下,可以稍微调整一下init指令相关的optional参数关闭git repo相关feature即可.另外--name也可以显示指定项目的名称,这样就不是默认的以项目文件夹名字作为项目名称了,删除当前的test文件夹,使用如下命令创建新的非git repo的uv项目:

          uv init test --description="a test uv project" --vcs=none --no-readme --python=3.11 --managed-python
          

          上面指令还会生成一个main.py的入口文件,然后在test目录下执行如下命令可以自动创建虚拟环境

          uv run main.py
          

          输出:

          Using CPython 3.11.13
          Creating virtual environment at: .venv
          Hello from test!
          

          可以看到虚拟环境文件夹.venvuv.lock文件都已经创建好了,至此项目初始化结束.

          这里简要说明一下刚创建好的项目是没有虚拟环境文件夹.venv的,除了使用uv run main.py之外还可以使用uv sync命令.

          本质上uv会查看本地是否有项目指定运行的python版本,比如本项目初始化的时候指定的是3.11版本的python,没有的话会先下载此版本python到~/.local/share/uv/python目录,然后再将此版本python"拷贝"到当前项目.venv文件夹中.

          可以在项目文件夹为上下文的命令行窗口中执行指令source .venv/bin/activate来激活此(使用deactivate则退出)虚拟环境.只是使用uv指令的时候是完全不需要显式激活虚拟环境这个操作.

          依赖管理

          uv 添加依赖有uv add命令,它会将依赖包安装到当前虚拟环境.venv中同时更新pyproject.toml中的dependencies配置.

          比如需要把最新的fastapi添加到项目中可以使用uv add "fastapi[standard]==0.116"添加一个固定版本的fastapi依赖包.当依赖安装好后查看pyproject.toml可以看到依赖已被添加

          dependencies = [
              "fastapi[standard]==0.116.0",
          ]
          

          实际开发过程中不会这么严格限制一个版本,需要支持能够获取最新的bugfix修订版本,所以一般依赖都会给定一个版本范围比如fastapi版本是0.116到0.117版本的最新修订版本,那么添加依赖指令变为uv add "fastapi[standard]>=0.116,<0.117",此命令会覆盖pyproject.toml中的版本约束,同时升级虚拟环境中安装的fastapi到0.116.x最新版本修.

          当然也可以直接修改pyproject.toml里面的fastapi依赖,如下

          dependencies = [
              "fastapi[standard]>=0.116,<0.117",
          ]
          
          

          然后再运行uv sync也能达到同样效果.但是最佳做法还是使用uv add增加或者修改现有的依赖.

          删除依赖则是uv remove指令.

          默认情况下如果安装依赖不指定版本约束,当前会安装最新版本,且dependencies会写入 dep >= latest version,比如执行如下指令

          uv add httpx
          

          查看dependencies

          dependencies = [
              "httpx>=0.28.1",
          ]
          

          此时安装的是最新版本,当让可以修改依赖的版本约束uv add "httpx>=0.28,<0.29"限制依赖版本为0.28.x的最新修订版本.

          uv中的依赖主要是三类,第一类是project.dependencies项目依赖,默认情况下使用指令uv add xxx的依赖都属于项目依赖,这些依赖在配置文件里面回添加到[project]配置段下的dependencies里面.这些依赖说白了都是代码中引入的包,代码运行时必不可少的包.

          第二种是dependency groups.就是开发所需依赖,不会被大包到项目中去,所以这类依赖也不会出现在[project]配置段中,只会出现在[dependency-groups]配置段中.

          有两种使用dependency groups的方法,第一种是uv add --dev xxx把依赖放入dev这个group,可以理解为dev是内置的dependency groups.比如将pytest这个包放入devgroup用于项目测试.

          uv add pytest --dev
          

          配置文件

          [dependency-groups]
          dev = [
              "pytest>=8.4.1",
          ]
          

          当然要删除在dev group中的这个包也需要加上–dev flag uv remove pytest --dev. 注意dev这个group在uv sync的时候也会拉取相应的依赖包.

          除了dev这个官方定义的组之外,还可以自定义组,使用指令uv add --group {group_name} {package}实现,比如:

          uv add --group lint ruff
          

          会创建一个lint的自定义组,且添加ruff依赖到此组.

          [dependency-groups]
          dev = [
              "pytest>=8.4.1",
          ]
          lint = [
              "ruff>=0.12.8",
          ]
          

          删除的话也需要添加flaguv remove ruff --group lint

          自定义组在使用uv sync时是不会拉取依赖的,需要配置[tool.uv]下面的default-groups追加自定义的group

          [tool.uv]
          default-groups = ["dev", "lint"]
          

          uv run

          uv run 指令是非常强大的指令. 下面全面说明它的用法:

          直接在命令行运行python代码片段

          它可以在命令行运行一段python代码片段,比如:

          uv run python -c "import sys;print(sys.executable)"
          

          输出当前解释器的位置

          ~/.local/share/uv/python/cpython-3.11.13-linux-x86_64-gnu/bin/python3.11

          直接运行项目中可执行脚本文件

          它还可以直接运行项目中的可执行脚本文件,比如项目中编程存在如下python脚本文件

          #!/usr/bin/env python
          #-*- coding:utf-8 -*-
          
          import httpx
          
          
          if __name__ == '__main__':
              print(httpx.__version__)
              pass
          

          输出结果:

          0.28.1
          

          这里用到了项目依赖httpx,但是可以在不需要显式激活当前虚拟环境的情况下直接打印当前虚拟环境中的依赖包信息.

          除了python脚本外,uv run还可以直接运行shell脚本,比如有如下foo.sh的shell脚本:

          python cli.py
          

          可以使用uv run bash foo.sh也能正常运行脚本获取打印的依赖包版本信息.

          再看一个细节,比如下面没有shebang行的python脚本sample.py

          if __name__ == '__main__':
              print('hello')
          

          如果直接命令行运行./sample.py肯定报错,大家都知道需要指定python解释器运行,比如python sample.py使用默认的解释器运行就不会报错.

          使用uv 运行此脚本则是命令uv run sample.py也能正确输出结果.uv运行python脚本的整个过程可以等价于如下操作:

          uv sync
          source .venv/bin/activate
          python sample.py
          

          当我们知道当前要运行的脚本是python时候,甚至可以直接用指令uv run -- python sample.py运行python脚本.

          运行python包中快捷指令

          我们知道有些包安装后是有在命令行运行的快捷指令的,比如环境中安装了fastapi[standard],那么在命令行里面就可以使用指令fastapi dev xxx.py以develop模式快速启动fastapi web app. 那么在uv管理的项目里面依然可以运行这种命令行指令.

          比如先在uv项目里面添加fastapi[standard]

          uv add "fastapi[standard]"
          

          编写一个fastapi web app 的 main.py文件:

          #-*- coding:utf-8 -*-
          
          from fastapi import FastAPI
          
          app = FastAPI(title='test')
          
          @app.get("/")
          async def index():
              return 'hello'
          

          uv 命令 develop 模式启动此app:

          uv run -- fastapi dev main.py --port 8090
          

          这里 uv run -- 中的-- 保证此字符后面都是fastapi的命令行指令与其运行参数,而不会被解析成uv run参数,非常关键。

          curl测试以及输出结果:

          curl http://localhost:8090
          "hello"
          

          因此基本上使用uv在命令行运行这些包中的快捷指令基本就是 uv run -- {package cli cmd} {cli params}这样的形式.

          uv项目本地运行调试细节

          上一部分完全探讨了uv run这个指令,这一部分详细探讨uv项目本地运行以及调试的细节.

          这里涉及到两种运行:

          • 1. vscode 中运行调试
          • 2. 本地命令行中调试运行细节

          vscode 中运行调试uv项目

          和之前笔者编程客栈用conda创建虚拟环境,在vscode中调试项目那一套基本操作类似.

          这里首先需要创建项目vscode debug所需的launch.json文件,文件最基本内容如下,可以根据需求进行更改.

          {
              // Use IntelliSense to learn about possible attributes.
              // Hover to view descriptions of existing attributes.
              // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
              "version": "0.2.0",
              "configurations": [
                  {
                      "name": "uv test single file",
                      "type": "debugpy",
                      "request": "launch",
                      "program": "${file}",
                      "console": "integratedTerminal",
                      "env": {
                          "OPENAI_BASE_URL": "https://api.deepseek.com",
                          "OPENAI_API_KEY": "xxxx"
                      }
                  },
                  {
                      "name": "uv test fastapi dev",
                      "type": "debugpy",
                      "request": "launch",
                      "module": "fastapi",
                      "console": "integratedTerminal",
                      "args": [
                          "dev", "main.py", "--port", "8090"
                      ],
                      "env": {
                          "OPENAI_BASE_URL": "https://api.deepseek.com",
                          "OPENAI_API_KEY": "xxxx"
                      }
                  }
              ]
          }
          

          笔者这里创建了两个相关配置uv test single file是项目单文件的debug配置,另一个uv test fastapi dev是整个fastapi项目develop模式启动调试的配置.其中比较关键的配置字段是module,需要指定为fastapi指令,其本质上还是相当于如下uv指令

          OPENAI_BASE_URL='https://api.deepseek.com' OPENAI_API_KEY=xxx uv run -- fastapi dev main.py --port 8090
          

          启动应用.

          vscode中需要给项目指定python解释器,这一步其实就是将当前虚拟环境中的python解释器配置为项目的python解释器,具体在vscode中做法为:

          View-->Command Pattle...-->Python:Select Interpreter-->{选择你的项目文件夹}-->解释器选择本项目虚拟环境中的python(./.venv/bin/python)
          

          这样vscode中debug选择Python Debugger: Debug using launch.json 选择uv test fastapi dev则是以develop模式启动整个fastapi项目本地调试,选择uv test single file则是调试项目中的单个文件.总之这是笔者总结下来的比较舒服的在vscode中开发uv项目的相关配置.

          命令行运行

          如果不使用vscode直接在命令行启动运行,那更简单了,直接参考前面讲到的uv运行python包中的快捷指令那一块使用uv run即可,具体指令如下:

          OPENAI_BASE_URL='https://api.deepseek.com' OPENAI_API_KEY=xxx uv run -- fastapi dev main.py --port 8090
          

          深入理解 uv lock, uv sync, uv lock

          uv lock 行为解析

          如果项目中没有uv.lock文件,那么会根据pyproject.toml解析出的精确依赖版本生成uv.lock文件;如果项目中存在uv.lock文件,根据解析出的精确依赖版本更新情况决定是否更新当前uv.lock文件.

          其中有一个指令uv lock --check则是解析pyproject.toml检查是否更行uv.lock.

          uv sync 行为解析

          uv sync首先需要去检查uv.lock文件是否是最新的,如果不是,则解析pyproject.toml更新uv.lock中依赖的精确版本,最后再根据uv.lock文件同步下载依赖包到虚拟环境.

          uv sync有两个关键参数--locked--frozen,他俩区别如下:

          参数说明
          –locked检查uv.lock是否是最新,如果要更新则报错,如果本身就是最新的则继续同步过程
          –frozen不进行uv.lock的更新检测,直接以uv.lock中依赖包版本同步虚拟环境中的依赖

          使用较多的指令是uv sync --locked,主要用于CI过程,保证提交的uv.lock文件中的依赖一定是最新版本,如果报错,证明uv.lock不是最新版本,需要开发提交最新的uv.lock到repo再触发ci过程.必须保证uv.lock一致性.

          uv run 行为解析

          可以简单理解默认的uv run之前要进行uv sync操作.

          其实–frozen参数其实是是在实际部署运行时候使用,uv run --frozen xxx表示不再check uv.lock是否更新,直接运行程序.因为前面CI过程已经保证uv.lock一致且依赖包已经最新.如果是容器部署的uv应用,基本上--froz编程客栈en参数会出现在容器入口程序中.

          uv项目的docker file

          根据上面的所有信息,基本上可以总结出一个uv项目的Dockerfile模板:

          FROM python:3.11.13-slim
          
          WORKDIR /app
          COPY ./ ./
          
          RUN pip install uv
          
          RUN uv sync --locked
          
          EXPOSE 8090
          
          ENTRYPOINT ["uv", "run", "--frozen", "--", "fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8090"]
          

          项目相关文件:

          1. main.py
          #-*- coding:utf-8 -*-
          
          from fastapi import FastAPI
          
          app = FastAPI(title='test')
          
          @app.get("/")
          async def index():
              return 'hello'
          
          1. pyproject.toml
          [project]
          name = "test"
          version = "0.1.0"
          description = "a test uv project"
          readme = "README.md"
          requires-python = ">=3.11"
          dependencies = [
              "click>=8.2.1",
              "fastapi[standard]>=0.116.1",
              "httpx>=0.28,<0.29",
              "openai>=1.99.6",
          ]
          
          [dependency-groups]
          dev = [
              "pytest>=8.4.1",
          ]
          lint = [
              "ruff>=0.12.8",
          ]
          

          构建镜像

          docker build -t uv-test:latest . --no-cache

          并且运行

          docker run --rm -p 8090:8090 --name uv-test uv-test:latest
          • 测试:
          curl http://localhost:8090
          
          • 输出:
          hello
          

          总结

          以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜