XnneHang

XnneHang

github
steam_profiles
douban

探索 uv (完結!) | 一個用 Rust 編寫的極速 Python 套件和專案管理器。

探索 uv#

為啥我找不到一個完整的關於 uv 的中文文檔或者教程。

是 pip 的黑惡勢力太強大了嗎?不,應該只是人們對於接受未知的事物抱有恐懼心理。

記錄我的探索過程和路線,所以會超級亂。

什麼是 uv?#

你把一個立方體的表面沿著一條線剪開,把所有的面展開,就得到了 uv 映射。你可以利用 uv 貼圖來給模型貼上紋理。

...

呸呸呸,這裡我們要講的 uv,是一個包管理器。

image-20250227111129976

這是 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 package -> uv lock -> uv run / uv sync -> uv 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 add -> uv lock -> uv 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 install -> uv 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-gnu.manylinux2014_x86_64.whl.metadata (62 kB)
Using cached numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64-gnu.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-gnu.manylinux2014_x86_64.whl.metadata (62 kB)
Using cached numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64-gnu.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"]

值得注意的是,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 畢業。

這個似乎長的離譜,但是我就寫了兩天。大部分都是廢話哈。

有時間可能會整理一個用法版本,但是我一直以來都只重探索不重整理。

所以.. 看緣分。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。