探索 uv (完结!) | An extremely fast Python package and project manager, written in Rust.

探索 uv

为啥我找不到一个完整的关于 uv 的中文文档或者教程。

是 pip 的黑恶势力太强大了吗?不,应该只是人们对于接受未知的事物抱有恐惧心理。

记录我的探索过程和路线,所以会超级乱。

什么是 uv?

你把一个立方体的表面沿着一条线剪开,把所有的面展开,就得到了 uv 映射。你可以利用 uv 贴图来给模型贴上纹理。


呸呸呸,这里我们要讲的 uv,是一个包管理器。<br

这是 uv 和 pip 的安装速度对比。

我们将在这里探索到一些 uv 的基础用法,取代 pip 的几种方式。以及会在后面尝试编译我自己的第一个 python package。(能够从 Github 安装并且运行即可)

呃另外,你可能会对我的记录方式感到不舒服,或者看得很难受,我理解,因为我按照我探索的路线写的,想到哪里实验一下,然后写下来。这也是我学的整个思路了。

后续可能会整理一个用法。

Refrence:

uv.lock + pyproject.toml vs requirements.txt

uv.lock 是在手动运行uv lock后生成的,根据pyproject.toml,并且一般情况下不应该被手动修改:

uv.lock is a human-readable TOML file but is managed by uv and should not be edited manually.
uv.lock 是一个人类可读的 TOML 文件,但由 uv 管理,不应手动编辑。

一开始我以为 uv.lock 可以替代 pyproject.toml 的 python dependency 配置,后来发现是增强了。

uv add packageuv lockuv run / uv syncuv run:

可以快速修改dependency并且验证是否兼容。

同时也支持从 Github 仓库直接安装第三方库,让包依赖处理非常精细。

中途更新 uv.lock 也是先更新pyproject.toml然后再uv lock.

此外,uv.lock 给人满满的安全感,如果你特地去看过 uv.lock 的内容,它精确到 source 从哪里安装。相比起来 requirements.txt 就非常潦草了。

Unlike the pyproject.toml, which is used to specify the broad requirements of your project, the lockfile contains the exact resolved versions that are installed in the project environment. This file should be checked into version control, allowing for consistent and reproducible installations across machines.
与用于指定项目总体要求的 pyproject.toml 文件不同,lockfile 文件包含项目环境中安装的精确解析版本。该文件应检查到版本控制中,以便在不同机器上进行一致和可重复的安装。

对于pyproject.toml+uv.lock的形式,人类可阅的部分是pyproject.toml,而uv.lock给机器阅读,而对于pip freeze生成的 requirements 来说,人也看不懂,机器也很头疼。

如果通过 uv add的形式,每个包的版本和库都是一点点搭建和确定起来的,而不是直接 freeze 的。

pyproject.toml 中会列出必须的包的以及版本,而依赖包会自动下载。这也是建议在uv add的时候只加入必要包而非依赖包。

pip freeze 的 requirements.txt 全是依赖包,根本不是给人读的。

有时候我会考虑手写依赖包,而手写 requirements.txt 却很考验耐心。

而当我懒得的时候:

我提供的 requirements.txt 是这样的:

pyaml
gradio

而且你必须重新创建一个干净的环境然后pip install -r requirements.txt才能验证这个是否完整。

相对于 uv adduv lockuv run script 所有的依赖都可以快速确定是否支持环境运行。因为uv run只根据uv.lock运行。

以及如果我用上述手写的requirements.txt(无版本标注),我确定我的使用者会在三个月后的某一天发现好像某些 api 被弃用或者最新版本的互相不兼容?

在 windows 上,它会下载最新版本的 pyaml 和 gradio。如果我很久没更新,大概率出问题。

如果在 linux 上面,这个就更离谱了,它会把所有版本的 pyaml 和 gradio 都下载一遍。我不清楚是不是 bug,反正它下了至少 5 个版本的 gradio,最后安装了几个我不清楚。

机缘巧合之下我决定入门 uv,从它的官方文档,下这个决定,也让我不得不克服之前的心理阴影,就是问 gpt 啥也不懂,反而被误导了好多天。

它让我用uv pip install uv.lock…我实在是。。。

不过在开始前,我还是需要自己摸一遍 uv,毕竟这个东西都找不到齐全点的中文文档。是 pip 的黑恶势力太强大了吗?不,应该只是人们对于未知的事物具有恐惧心理。我挺早就想了解一下,但是拖到现在,还是机缘巧合。

安装 uv 以及最终建议。

https://docs.astral.sh/uv/getting-started/installation/

我最终建议是全局安装 uv + 使用 pyproject.toml + uv.lock 的方式来管理项目(python lib)。

具体参考:

https://github.com/yutto-dev/yutto

https://github.com/MrXnneHang/yutto-uiya

或者图方便只是用来加速安装也可以简单用 uv+conda 来替代 pip+conda。用法上,就是把所有的:

pip installuv pip install.

从 uv run 开始: uv run python/yutto/… 的可执行文件位于哪里呢?

如果不考虑那个人博客里写的 uvv 那种全局做法,正常的 uv 的环境位于项目根目录的.venv下方。

写上 uv run 的就相当于之前在 conda 指定虚拟环境下可以直接用 python/yutto 去访问我们虚拟环境中的可执行文件。

python 一般在 .venv/bin 下方。

独立使用 uv 创建一个 venv。

值得一提。在 linux 上面安装 miniconda 之初,会让我们选择是否开启auto_activate_base,即每次进入终端的时候是否会默认激活 base 环境。当我们尝试对 uv 进行独立使用的时候,应该考虑先禁用掉这个选项:

不然你会发现,创建的 venv 都是当前虚拟环境的 python 版本。

conda config --set auto_activate_base false

然后新开一个终端,就不会自动激活 base 环境了。

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv venv
Using CPython 3.13.0
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ ls .venv/
bin  CACHEDIR.TAG  lib  lib64  pyvenv.cfg
xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ ls .venv/bin/
activate      activate.csh   activate.nu   activate_this.py  pydoc.bat  python3
activate.bat  activate.fish  activate.ps1  deactivate.bat    python     python3.13

似乎直接创建了一个 3.13 的 python 基础环境。

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python tmp.py
Hello UV!

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run tmp.py
Hello UV!

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python
Python 3.13.0 (main, Oct 16 2024, 03:23:02) [Clang 18.1.8 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

可以看见似乎正常工作。

而且,我在创建环境的时候,速度快的离奇?半秒?

似乎是这个原因:

If Python is already installed on your system, uv will detect and use it without configuration. However, uv can also install and manage Python versions. uv automatically installs missing Python versions as needed — you don't need to install Python to get started.
如果系统中已经安装了 Python,uv 将检测并使用它,而无需配置。uv 会根据需要自动安装缺失的 Python 版本,因此无需安装 Python 即可开始使用。

参见:Install Python

我们也会利用这个特性来直接从 conda 中直接获取我们需要的 python 版本,具体见下一节。

如何安装指定版本的 python?

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv python install 3.12
Installed Python 3.12.9 in 53.73s
 + cpython-3.12.9-linux-x86_64-gnu

这个又安装了一遍。根据上面可以判断是因为我系统中不具有 3.12 的 python 版本。

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python
Python 3.13.0 (main, Oct 16 2024, 03:23:02) [Clang 18.1.8 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python3.12
Python 3.12.4 (main, Jul  9 2024, 09:31:23) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python3.11
error: Failed to spawn: `python3.11`
  Caused by: No such file or directory (os error 2)

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ ls .venv/bin/
activate      activate.csh   activate.nu   activate_this.py  pydoc.bat  python3
activate.bat  activate.fish  activate.ps1  deactivate.bat    python     python3.13

可以看到,它安装 3.12 似乎并不是安装在.venv下.当我删除了.venv之后,再次运行uv run python3.12,它依然可行。说明它是全局安装的。

这其实存在一些可怕的隐患,全局的 python 总是会存在一些可以钻的漏洞。比如我使用uv run python -m pip install matplotlib。我就可以直接污染全局环境。

所以,这里我个人觉得更为可行的做法是,利用 conda 来管理这种 3.10,3.11,3.12 的环境,当我们需要的时候,从 conda 那边复制环境到./venv.后来我们所有的环境更改都针对./venv。

为 venv 指定 python 版本:

因为相对于系统全局的 python, conda 的虚拟环境更为安全并且容易管理。我们甚至可以自定义不同的起始包,比如起始 selenium,webdrier 来做爬虫工作的,或者起始 numpy,matplotlib 来做数据分析工作的。然后在用到的时候复制到.venv 中做基础环境。

我后半段的想法是错的,因为uv venv只会继承 python,不会继承 package。所以,这个想法是不可行的。下一个内容会证明这一点。

这点上我和那个作者不谋而合:

conda activate 11
uv venv --seed -p 3.11
conda deactivate
uv pip install -r requirements.txt
source ./.venv/bin/activate
python main.py

参数解析:

-p, --python <PYTHON>
    The Python interpreter to use for the virtual environment. [env: UV_PYTHON=]
--seed
    Install seed packages (one or more of: `pip`, `setuptools`, and `wheel`) into the virtual
    environment [env: UV_VENV_SEED=]

我发现并没有我想象的复制发生,可能并非复制环境,只是继承了 Python。

它利用了一点,就是在 uv venv运行的时候,默认检测系统首选 python 作为 venv 的 python。而当我们conda activate 11之后,我们默认运行python XXX运行的是 conda 的 python。所以,uv venv就会继承这个 python。

至于它会不会继承 package.来测试一下。

测试 venv 创建是否会继承虚拟环境的 package

先说结论,不会,只能继承 python。

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ conda activate numpy
(numpy) xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ python
Python 3.10.16 (main, Dec 11 2024, 16:24:50) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> exit()

(numpy) xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ conda deactivate
xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ conda activate numpy

(numpy) xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ python
Python 3.10.16 (main, Dec 11 2024, 16:24:50) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> numpy.__version__
'2.2.2'
>>> exit()

(numpy) xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv venv -p 3.10 --seed
Using CPython 3.10.16 interpreter at: /home/xnne/miniconda3/envs/numpy/bin/python3.10
Creating virtual environment with seed packages at: .venv
 + pip==25.0.1
 + setuptools==75.8.0
 + wheel==0.45.1
Activate with: source .venv/bin/activate

(numpy) xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ conda deactivate
xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python
Python 3.10.16 (main, Dec 11 2024, 16:24:50) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'numpy'
>>>

Using CPython 3.10.16 interpreter at: /home/xnne/miniconda3/envs/numpy/bin/python3.10 这说明确实是用到了我们的 conda 环境的 python。但是也只用到了Interpreter.

使用uv run pip installuv pip install的区别:

uv run pip install vs pip install:

先说结论,uv run pip install会安装到.venv 中,和 uv run python -m pip install 是一个效果。

而前面我们猜测,这么做和正常使用 pip install 几乎没有区别,只不过在我们使用 conda 的时候,虚拟环境中使用 pip install 并不会污染全局环境,因为前面提到当我们激活虚拟环境,虚拟环境的优先级高于系统环境,所以 pip install 会安装在虚拟环境中。

而我们的 uv 只创建了一个局部的.venv。

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python
Python 3.10.16 (main, Dec 11 2024, 16:24:50) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'numpy'
>>> exit()

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run pip install numpy
Collecting numpy
  Using cached numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)
Using cached numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.4 MB)
Installing collected packages: numpy
Successfully installed numpy-2.2.3

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run python
Python 3.10.16 (main, Dec 11 2024, 16:24:50) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> numpy.__version__
'2.2.3'
>>>

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ ls .venv/lib/python3.10/site-packages/
_distutils_hack           numpy.libs            __pycache__                  _virtualenv.py
distutils-precedence.pth  pip                   setuptools                   wheel
numpy                     pip-25.0.1.dist-info  setuptools-75.8.0.dist-info  wheel-0.45.1.dist-info
numpy-2.2.3.dist-info     pkg_resources         _virtualenv.pth

而当我们直接运行pip install时,会发现我的系统环境下实际上并没有pip =-=/.

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ pip uninsatll numpy
bash: pip: 未找到命令
xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ pip install numpy
bash: pip: 未找到命令

这说明了,uv 并不像 conda 那样子直接为我们隔绝了系统环境,而是在当前目录下创建了一个.venv,然后所有uv run XXX都是在这个.venv 下进行操作。

uv pip install vs uv run pip install:

先说结论,两者都是对.venv下的环境操作。

但是,uv run pip install是用 pip 来安装环境,而uv pip install是用 uv 来安装环境。

我们简单看一下速度比较:

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ time uv run pip install numpy
Collecting numpy
  Using cached numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)
Using cached numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.4 MB)
Installing collected packages: numpy
Successfully installed numpy-2.2.3

real    0m3.090s
user    0m1.770s
sys     0m0.163s

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv pip uninstall numpy
Uninstalled 1 package in 55ms
 - numpy==2.2.3

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ time uv pip install numpy
Resolved 1 package in 16ms
Installed 1 package in 31ms
 + numpy==2.2.3

real    0m0.064s
user    0m0.007s
sys     0m0.054s

好家伙,速度不是一个量级的。

也就是说,我们平时安装环境的时候多用uv pip install即可.

稍微总结一下如何用 uv 来取代 pip 的工作。

如果你是用 miniconda ,并且也只是想要用 uv 来加速一下包安装。那么,每次在创建环境后:

pip install uv
uv pip install -r requirements.txt/packagename

这样应该是最简单的。

如果你和我一样有做整合包的习惯,希望能够把环境打包在项目根目录。那么可以尝试:

conda activate 10
uv venv --seed -p 3.10
conda deactivate
uv pip install -r requirements.txt/packagename

这个 uv 需要全局安装。参见上面的链接。

以及,当我们 uv 无法安装某个包的时候,我们就需要重新用 pip,这也是我上面花费大量时间来聊uv pipuv run pip的原因。

例如:

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run __main__.py
Traceback (most recent call last):
  File "/home/xnne/code/yutto-uiya/src/uiya/__main__.py", line 3, in <module>
    import gradio as gr
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/gradio/__init__.py", line 3, in <module>
    import gradio._simple_templates
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/gradio/_simple_templates/__init__.py", line 1, in <module>
    from .simpledropdown import SimpleDropdown
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/gradio/_simple_templates/simpledropdown.py", line 7, in <module>
    from gradio.components.base import Component, FormComponent
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/gradio/components/__init__.py", line 1, in <module>
    from gradio.components.annotated_image import AnnotatedImage
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/gradio/components/annotated_image.py", line 14, in <module>
    from gradio import processing_utils, utils
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/gradio/processing_utils.py", line 120, in <module>
    sync_client = httpx.Client(transport=sync_transport)
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/httpx/_client.py", line 697, in __init__
    self._mounts: dict[URLPattern, BaseTransport | None] = {
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/httpx/_client.py", line 700, in <dictcomp>
    else self._init_proxy_transport(
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/httpx/_client.py", line 750, in _init_proxy_transport
    return HTTPTransport(
  File "/home/xnne/code/yutto-uiya/src/uiya/.venv/lib/python3.10/site-packages/httpx/_transports/default.py", line 191, in __init__
    raise ImportError(
ImportError: Using SOCKS proxy, but the 'socksio' package is not installed. Make sure to install httpx using `pip install httpx[socks]`.

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv pip install https[socks]
  × No solution found when resolving dependencies:
  ╰─▶ Because there are no versions of https[socks] and you require https[socks], we can conclude that
      your requirements are unsatisfiable.

我无法使用uv pip install来安装httpx[socks],这个时候就得用回uv run pip install了。

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run pip install httpx[socks]
Requirement already satisfied: httpx[socks] in ./.venv/lib/python3.10/site-packages (0.28.1)
Requirement already satisfied: anyio in ./.venv/lib/python3.10/site-packages (from httpx[socks]) (4.8.0)
Requirement already satisfied: certifi in ./.venv/lib/python3.10/site-packages (from httpx[socks]) (2025.1.31)
Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.10/site-packages (from httpx[socks]) (1.0.7)
Requirement already satisfied: idna in ./.venv/lib/python3.10/site-packages (from httpx[socks]) (3.10)
Collecting socksio==1.* (from httpx[socks])
  Using cached socksio-1.0.0-py3-none-any.whl.metadata (6.1 kB)
Requirement already satisfied: h11<0.15,>=0.13 in ./.venv/lib/python3.10/site-packages (from httpcore==1.*->httpx[socks]) (0.14.0)
Requirement already satisfied: exceptiongroup>=1.0.2 in ./.venv/lib/python3.10/site-packages (from anyio->httpx[socks]) (1.2.2)
Requirement already satisfied: sniffio>=1.1 in ./.venv/lib/python3.10/site-packages (from anyio->httpx[socks]) (1.3.1)
Requirement already satisfied: typing_extensions>=4.5 in ./.venv/lib/python3.10/site-packages (from anyio->httpx[socks]) (4.12.2)
Using cached socksio-1.0.0-py3-none-any.whl (12 kB)
Installing collected packages: socksio
Successfully installed socksio-1.0.0

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv run __main__.py
* Running on local URL:  http://127.0.0.1:7860

To create a public link, set `share=True` in `launch()`.

solve.

uv lock,锁死环境。

运行这个我们首先需要一个pyproject.toml

这是一个 python+ruff 的简单配置: https://github.com/MrXnneHang/yutto-uiya/blob/gradio-webui/pyproject.toml

之后你运行:

xnne@xnne-PC:~/code/yutto-uiya/src/uiya$ uv lock
Using CPython 3.13.0
Resolved 1 package in 4ms

你会发现,好像什么包都没锁定,假如,你的pyproject.toml中没有提供:

[dependency-groups]

# dev = [
#   "pyaml>=25.1.0",
#   "gradio>=5.16.1",
# ]

但是如果需要手写 pyproject.toml 似乎太累了,能不能直接从.venv 中提取呢?

滞后导出不是好习惯。就好像 pip freeze在最后导出时局限性很大,导出很多依赖库让依赖变得不可读。
你应该逐步探索需要的具体第三方库,以及它的版本。至于依赖库,就不用写了。
最后你把它写在pyproject.toml中,并且 uv lock ,uv sync(可选),uv run 进行确认可运行和兼容。

lock env 之后可能出现的问题。 | .venv 目录变更 | 包名称不同

比如我__main__.py和 requirements.txt 不在根目录。我一开始创建的.venv位于./src/uiya(相对于根目录。)

uv.lock需要pyproject.toml,pyproject.toml需要在根目录下。然后 uv.lock 会自动搜索父文件夹下的pyproject.toml并且又重新创建了一个.venv

也就是说一开始我的.venv位于./src/uiya,现在变成了./

这个最初给我带来了一些困扰,因为我发现我无法通过uv run来使用我子文件夹下的.venv,并且前面提到,uv 无法为我安装httpx[socks],所以我需要手动安装。

所以,这里的做法最好是,在一开始就创建 pyproject.toml.那么uv run,uv pip install针对的对象都是根目录(和 pyproject.toml 相同目录)下的.venv

这是一个简单的配置:

name = "yutto-uiya"
version = "1.0.2"
description = ""
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "gradio==5.16.1",
    "pyaml==25.1.0",
    "socksio==1.0.0",
    "yutto",
]
authors = [{ name = "MrXnneHang", email = "[email protected]" }]
keywords = []
license = { text = "MIT" }
classifiers = [
  "Operating System :: OS Independent",
  "License :: OSI Approved :: MIT License",
  "Typing :: Typed",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: Implementation :: CPython",
]

[project.urls]
Homepage = "https://github.com/MrXnneHang/yutto-uiya"
Documentation = "https://github.com/MrXnneHang/yutto-uiya"
Repository = "https://github.com/MrXnneHang/yutto-uiya"
Issues = "https://github.com/MrXnneHang/yutto-uiya/issues"

[project.scripts]
uiya = "uiya.__main__:main"

[tool.uv.workspace]
members = ["packages/*"]

[tool.uv.sources]
yutto = { git = "https://github.com/MrXnneHang/yutto.git", rev = "depndency-adjust" }

[tool.pyright]
include = ["src/uiya", "tests"]
pythonVersion = "3.10"
typeCheckingMode = "strict"

[tool.hatch.build.targets.wheel]
packages = ["src/uiya"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

值得注意的是,dependency 和 source 这些并不需要手写。

这些可以通过uv add gradio==5.16.1来添加。

也支持 github 仓库比如:

uv add git+https://github.com/MrXnneHang/yutto.git@depndency-adjust

所以, uv 实际上是推荐配合 pyproject.toml 使用的,如果希望用来管理包版本,因为uv lock必须在存在pyproject.toml的情况下才可以运行。

另外,虽然 dependency 大部分可以直接从 requirements 里面搬运迁移。但是应该注意一些包命名不同的情况,比如socksio在 pip 里面的命名是httpx[socks]

但是一旦你的pyproject.tomluv.lock 都做好了,那么给别人运行的话只需要uv run script即可。

另外,锁死的版本可以让别人不用考虑环境版本兼容问题,友好你我他。

在你需要更新 uv.lock 的时候,你需要先uv add packagename==version,然后uv lock

我建议锁死版本不用留余地。此外,uv lock 后,你也不必运行uv pip install来手动装包了,你可以直接uv run script,它会根据uv.lock自动配置包,只要你能这么运行起来,那么别人也可以。当然一些 windows 和 Linux 跨越时用了 win32api 的不算。

为了跨系统友好,应该避免用到系统依赖的底层 api.

其实到了这一步,uv 基本上已经快毕业了,为啥我突然这么自信,因为我经过了一番实战,把我原本一个结构混乱的项目整理并且打包了成了可以直接从 Github 上安装的形式。

https://github.com/MrXnneHang/yutto-uiya

关键在于 pyproject.tomluv.lock。其中 uv.lock 是根据 pyproject.toml 自动生成的。

实际上动手比光在这看要快得多。而且理解会突然深入,因为你会遇到各种各样的问题。抄作业的过程很痛苦,抄完了却非常舒适。

顺带一提,我抄的是:

https://github.com/yutto-dev/yutto

uv 运行脚本。

可执行文件

前面提到,可以uv run pip,uv run python等等来运行bin下方的内容。

这里不过多赘述了。

Python 脚本。

这里我会分为有pyproject.toml和没有pyproject.toml两种情况。

pyproject.toml+uv.lock的情况

这个运行的话,实际上是根据 uv.lock 的内容来运行的,它会在uv.lock同级目录自动创建一个.venv,然后在这个.venv自动配置环境并且运行。

所以,这个的模式一般是:

运行脚本:

git clone XXX
uv run XXX

什么都不用管,要是跑不起来绝对不是我的问题,绝对是维护者的问题。

如果维护者的话,一般就是这样:

uv add package==version
uv lock
uv run XXX # 如果成功,就上传,否则就考虑调整package版本

最后调试成功,如果你是个好人,你会选择把 uv.lock 一起上传,并且跟你的使用者说,你可以直接uv run XXX运行。

因为,大部分的人真的不清楚 uv 这东西咋用。他们会找半天 requirements.txt,没找到,然后尝试了半天uv pip install uv.lock

然后怎么也搞不清uv run pipuv pip。我以前就是这样的,甚至都有心理阴影了。

没有pyproject.toml+uv.lock的情况

这个比较类似于直接把 pip 换成 uv pip 进行加速。

其实目前我更加推荐用pyproject.toml + uv.lock 进行管理。

但是你也依然可以使用requirements.txt.

这种情况,没必要考虑.venv ,直接在 conda 里面装 uv 和环境就好了。

原理:如果你激活了 conda 环境,那么uv pip install 等等也默认会装在 conda 环境里。

当然你实在需要一个独立的.venv,那么也可以考虑这样的做法:

conda activate 11 # python 3.11 的基础环境
uv venv --seed -p 3.11
conda deactivate # 前面提到,必须退出环境否则会装在conda里。
uv pip install -r requirements.txt

这样的话,你的.venv就是独立的了。但是它有个局限性,就是,你似乎只能创建在当前目录下并且在此访问它?如果运行 uv run XXX 的时候。

它并不像有pyproject.toml+uv.lock的情况,即使.venv在根目录,你也可以在项目目录下的任何地方访问它。

所以我个人只推荐pyproject.toml+uv.lock的模式和conda + uv取代conda + pip的模式。

当我打包好项目后,如何反复编译?

最开始我运行的方式是:

uv run __main__.py

这个适用于开发阶段,反复调试。

后来进入编译调试阶段。即我希望可以直接运行我的可执行文件。这里是uiya

我的做法是很蠢的,上传 github,然后用:

conda create -n test-uiya python=3.10
conda activate test-uiya
uv pip install git+https://github.com/MrXnneHang/yutto-uiya.git@gradio-webui

然后每次更新代码后我都重新uv pip install一遍,好在它会检测最新分支且不会被 cache 影响不需要 uninstall.

它确实成功了,在我第一次安装成功并且运行起来的时候我非常兴奋。

但是后来这个方式让我非常疲惫。

因为每次都需要先把代码上传到 github,然后再重新安装一遍。这个过程非常繁琐。

我希望可以一行代码就自动更新。

这里就得提到本地编译和更新了。上面那个方法我就称他为远程编译吧=-=。

本地编译生成可执行程序。

emmmmm,我应该咋说呢?

好像不需要任何指令哈。

万能的uv run

你只需要uv run uiya就可以了。

它会根据你最新的代码运行,因为这个时候实际上应该处于一种未编译的状态,而你在pyproject.toml中配置了uiya = "uiya.__main__:main",所以它会自动运行你的__main__.py
`

而特意编译好像也没啥必要哈,就像 vue 可以实时预览一样,这样的效率总会比编译一下再看一下要高。

我从最初就疑惑的 uv sync

我以为它会在我们的包管理中起到很重要的作用,但是实际上好像不用也可以?

sync 的翻译是同步,同步的是环境。

可能是在 uv lock 后同步一下?但是我记得好像uv run每次都会检查uv.lock的内容。

我们这里来看一下一个用例:

如果你想要本地调试,最佳实践是从 GitHub 上下载最新的源码来运行

git clone [email protected]:yutto-dev/yutto.git
cd yutto/
uv sync
uv run yutto -v

https://github.com/yutto-dev/yutto/blob/main/CONTRIBUTING.md

可以推测出来这个 sync 是用来安装环境的,但是现在似乎可以直接uv run yutto了。

也就是说,uv sync目前应该已经集成到了uv run的步骤中了,所以我们不需要手动运行uv sync

当然偶尔碰到奇怪的环境问题可以uv sync来瞅瞅咋回事。

按照我们之前的实验,uv run生成的.venvpyproject.toml以及uv.lock同一目录。

我们再测一下uv sync:

xnne@xnne-PC:~$ git clone [email protected]:MrXnneHang/yutto-uiya.git
正克隆到 'yutto-uiya'...
remote: Enumerating objects: 898, done.
remote: Counting objects: 100% (57/57), done.
remote: Compressing objects: 100% (39/39), done.
remote: Total 898 (delta 23), reused 36 (delta 12), pack-reused 841 (from 1)
接收对象中: 100% (898/898), 389.53 KiB | 429.00 KiB/s, 完成.
处理 delta 中: 100% (512/512), 完成.

xnne@xnne-PC:~$ cd yutto-uiya/

xnne@xnne-PC:~/yutto-uiya$ uv sync
Using CPython 3.13.0
Creating virtual environment at: .venv
Resolved 64 packages in 2ms
      Built yutto-uiya @ file:///home/xnne/yutto-uiya
Prepared 1 package in 1.49s

xnne@xnne-PC:~/yutto-uiya$ ls .venv/
bin/          CACHEDIR.TAG  .gitignore    lib/          lib64/        pyvenv.cfg

这里看到,它确实是在装环境,并且装在了根目录的.venv下。

推测正确。

不过这里有个神奇的一点,是 python 版本,它会默认找 source 里面排最前面的,如果要控制 python 版本的话,可以参考前面conda激活再退出的做法。

(10) xnne@xnne-PC:~/yutto-uiya$ uv sync -p 3.10
Using CPython 3.10.16

当然,似乎也没必要用到 conda,因为 uv 有自己的全局解释器,如果指定的版本没有会自动下载补上。就是位置比较诡异。

如果环境有问题,可以考虑删掉.venv然后重新uv sync,要是还不行,那就去找维护者=-=。

恭喜, uv 毕业。

这个似乎长的离谱,但是我就写了两天。大部分都是废话哈。

有时间可能会整理一个用法版本,但是我一直以来都只重探索不重整理。

所以…看缘分。

41 个赞

佬太强了,学习学习

1 个赞

大部分大模型可能没有关于uv的训练语料,所以它会根据经验来。

所以尽量不要尝试问大模型关于 uv 的东西,当时问的一头雾水。。

以及因为我的探索是循序渐进的,中间一部分可能存在一点错误的说法,而且学的过程还有点曲折。但是我的运行结果都是实际 copy 下来的。

感谢佬友的文章!立马加入知识库 :tieba_091:

1 个赞

也感谢 uv 让我完整构建了自己的第一个 python lib。

一直使用uv, uv add $(pip freeze) 非常好用

1 个赞

我几乎不用 uv lock,只用 uv sync 来同步环境,所以不太理解 uv lock 的关键性作用是什么?
关于锁定指定版本的 python,可以用命令 “uv python pip [你要装的版本]”,会自动修改文件 “.python-version“。
关于安装包的话,用 uv add 和 uv remove 可以自动修改 pyproject.toml。我不太理解 uv pip install 这样做带来的好处是什么?

我个人觉得 pip freeze导出的很多依赖包会让dependency变得不可读。
我习惯手写。

1 个赞

可以用ai 搜索加上互联网的搜索结果,并且在问题中加上一些设定,比如“python的包管理工具uv怎么样?”

1 个赞

我一般是为了复制环境,因为不同包之间的依赖一直在变化。可能过段时间就出conflict了。

1 个赞

我的理解是。
uv sync被集成在了uv run里面了。
因为我测试了一下这两个流程。

uv add package
uv lock
uv run main.py
uv add package
uv lock
uv sync

这两个流程最终生成的环境的位置和内容都是完全一致的。都在pyproject.toml同目录.venv下方。
另外,关于 uv pip install
它好像就是用 uv 自己的下载方式安装的。
uv run pip install(直接用.venv下的pip安装)不同。
命名成uv pip install可能只是为了让用户好上手?和pip实际上无关。
因为uv install好像被占用了,但我没研究那个做啥的。

1 个赞

我会自己写 requirements.txt (会用现成的库 pipreqs 先大概生成一下,因为版本有时候不准确,需要自己修改一下),然后用 uv add -r requirements.txt。

还有个好处是,当你有多个不同使用需求下需要的多组可选的依赖,可以写多个 requirements,然后例如执行:uv add -r requirements-plot.txt --optional plot,就可以添加一组名为 plot 的 optional dependencies。

1 个赞

学习了,一直想着找篇文档了解一下uvtieba_002

1 个赞

就是废话超级多就是了 :rofl:

好文,:rose::rose::rose:

1 个赞

uv~~ next!

1 个赞

太强了!支持大佬!

1 个赞

佬厉害!我也在日常生活中使用uv,在探索这类方案的时候,往往会遇到困惑和障碍,很需要这样的分享。

1 个赞

看到好多推荐uv的

1 个赞

学习一下,这好像是新版pycharm推出的吧

1 个赞