uv 取代了之前的 pip、還加上 Python 本體的版本管理,概念上大概像是 Node.js 生態系的 fnm 加 pnpm;簡單試了一下新專案、感覺執行速度也不錯,真是時代在進步啊……希望趕快推廣開來。
在 Windows 上安裝 uv
官方的安裝說明文件在此:Installation | uv
雖然官方最推薦的是直接用 PowerShell 的 irm | iex 執行,不過若是比較擔心資安問題、也可以透過 WinGet 安裝,至少有多一層 Microsoft 的認證機制、避免中間人攻擊。
winget install --id=astral-sh.uv -euv --version # 顯示版本以驗證安裝成功 而且如果用 PowerShell irm | iex 安裝,裝好後還要再多一步驟把它安裝到的路徑 %USERPROFILE%\.local\bin 加入環境變數 PATH;而透過 WinGet 安裝會裝在 WinGet 已經設好的 PATH 路徑下、免去這一步驟。
透過 uv 安裝 Python
uv python list # 查看所有可安裝與已安裝的版本uv python install # 安裝最新的穩定版本uv python install 3.13 # 安裝指定版本安裝為全域 Python
上一步安裝的 Python 只是 uv 內部使用,不能在任意目錄呼叫;如果想把透過 uv 安裝的 Python 變成任意目錄可呼叫,在我目前使用的 uv 版本 0.11.8 要再加上兩組參數變成這樣(未來此功能進正式版後就不用加 preview-features 參數了):
uv python install --default --preview-features python-install-default # 安裝最新的穩定版本、並設為全域uv python install 3.13 --default --preview-features python-install-default # 安裝指定版本、並設為全域python --version # 顯示版本以驗證實際細節
我在這串嘗試過程中發現了一點端倪,追查下去之後終於釐清了實際執行細節的來龍去脈,在這裡展開聊一下:
- 首先執行
uv python dir可以查到 uv 管理的 Python 實際安裝的目錄,以下用<UV_PYTHON_INSTALL_DIR>代稱(實際也確實可以用UV_PYTHON_INSTALL_DIR這個環境變數作為 uv 管理 Python 目錄的設定) - 執行
uv python install 3.13時,實際是找到該版號下最新穩定版「3.13.13」後、安裝在<UV_PYTHON_INSTALL_DIR>\cpython-3.13.13-windows-x86_64-none目錄下,然後在旁邊建立一個 junctioncpython-3.13-windows-x86_64-none、以讓 uv 未來要找 3.13 版時順著這個 junction 找到 3.13.13 實際 Python 執行檔。 - 執行
uv python install 3.13 --default時,實際上是做了兩件事:- 在
%USERPROFILE%\.local\bin產出「python3.13.exe」、「python.exe」、「python3.exe」三個 exe 檔,雖然看起來都是 exe 檔,但內容其實是「python3.13.exe」指向<UV_PYTHON_INSTALL_DIR>\cpython-3.13-windows-x86_64-nonejunction、「python.exe」和「python3.exe」指向「python3.13.exe」 - 檢查目前環境變數 PATH 裡是否有
%USERPROFILE%\.local\bin,如果沒有、就在使用者環境變數 PATH 的最前面加上去- 不過新加的環境變數 PATH 要在未來開啟的 terminal 才會生效,所以當下會看到 uv 警告說
`%USERPROFILE%\.local\bin` is not on your PATH.
- 不過新加的環境變數 PATH 要在未來開啟的 terminal 才會生效,所以當下會看到 uv 警告說
- 靠著這兩個行為,未來開啟的 terminal 裡直接呼叫
python或python3時,就可以沿線找到 uv 管理的 3.13.13 版本來執行了。
- 在
- 若是接著再執行
uv python install --default、而此時的最新版本是 3.14.4,而且之前已經執行過uv python install、所以其實已經安裝好<UV_PYTHON_INSTALL_DIR>\cpython-3.14.4-windows-x86_64-none,實際則是做了以下的事:- 找到最新穩定版「3.14.4」,然後發現 uv 管理的 Python 裡已經有此版本了、旁邊也已有 3.14 版的 junction,就不再執行安裝
- 到
%USERPROFILE%\.local\bin產生一個「Python3.14.exe」、指向<UV_PYTHON_INSTALL_DIR>\cpython-3.14-windows-x86_64-nonejunction - 到
%USERPROFILE%\.local\bin產生「Python.exe」與「Python3.exe」替代掉舊的,內容是指向「Python3.14.exe」 - 檢查目前環境變數 PATH、發現已有
%USERPROFILE%\.local\bin,不再動作
- 這樣沒有改到環境變數 PATH、所以現有 terminal 也直接生效,呼叫
python或python3或python3.14會沿線找到 uv 管理的 3.14.4 版本來執行,而呼叫python3.13還是會呼叫到上一步產生的「Python3.13.exe」、沿線找到的也就還是 uv 管理的 3.13.13 版本。
使用 uv 管理專案
-
初始化
Terminal window uv init # 將目前工作目錄初始化為 Python 專案uv init "<path>" # 將 <path> 目錄初始化為 Python 專案;若 <path> 不存在將自動建立目錄- 其實已預設自帶
--app參數、表示初始化為 Python 專案 - 會建立兩個檔案:
- pyproject.toml:記錄專案資訊,大約相當於 npm 的 package.json
- .python-version:記錄此專案使用的 Python 版本,大約相當於 nvm 的 .nvmrc
- 這兩個檔案都不是 uv 獨有、而是許多工具都支援的格式。
- 預設會找目前 uv 管理的 Python 版本裡最新的那一個,一方面作為此專案的最低要求 Python 版本、記入 pyptoject.toml 裡,另一方面作為此專案須使用的 Python 版本記入 .python-version
- 預設會附帶
git init和建立 .gitignore,如果不想、可以加參數--vcs none - 預設會建立一個空的 README.md 並加入 pyptoject.toml,如果不想、可以加參數
--no-readme
- 其實已預設自帶
-
切換此專案的 Python 版本
Terminal window uv python pin 3.13- 會將 .python-version 內容改為指定的版本
- 如果指定的版本超出 .pyproject.toml 指定的 requires-python 範圍,會被擋下來
-
管理依賴
Terminal window uv add requests # 加入 requests 的最新穩定版作為正式依賴uv add requests==2.32.5 # 加入 requests 的 2.32.5 版本作為正式依賴uv add --dev pytest # 加入 pytest 作為開發依賴uv add --group lint ruff # 加入 ruff 至「lint」依賴群組uv remove requests # 移除 requests 依賴- 若沒有指定版本,就會去找最新穩定版本
- 比 npm 多了 group 的功能——可以把依賴細分成正式、開發以外更多其他的群組,其他接手此專案的開發者可以選擇性批次安裝哪些群組裡的依賴
- 會更新 pyproject.toml 與 uv.lock,並把指定 package 安裝至此專案下的
.venv(其包含了 npm 的 node_modules 的功能) - 「uv.lock」是 uv 專屬的檔案,大約相當於 npm 的 package-lock.json
-
接手專案後在本地安裝依賴
Terminal window uv sync # 安裝正式與開發依賴,不含其他 groupuv sync --no-dev # 只安裝正式依賴uv sync --group lint # 只安裝「lint」group 的依賴uv sync --all-group # 安裝所有依賴 -
執行
Terminal window uv run main.py- 會用專案內的 venv 執行,其中這個 venv 的 Python 版本由 .python-version 指定、包含的 package 由 pyproject.toml 與 uv.lock 指定
使用 uv 管理單一 py 檔腳本
-
初始化
Terminal window uv init .\test.py --script # 建立 test.py py 檔、初始化為 Python 腳本- 只會建立 test.py 單一 py 檔,開頭會有以
# ///包裹的一段註解(如下)、是此腳本的 Python 版本與依賴資訊# /// script# requires-python = ">=3.14"# dependencies = []# ///- 這也不是 uv 專屬、而是許多工具支援的格式。
- 只會建立 test.py 單一 py 檔,開頭會有以
-
管理依賴
Terminal window uv add --script test.py requests # 加入 requests 的最新穩定版作為正式依賴uv add --script test.py requests==2.32.5 # 加入 requests 的 2.32.5 版本作為依賴uv remove --script test.py requests # 移除 requests 依賴- 在 script 模式下就沒有開發依賴和 group 功能了
- 可用
uv lock --script test.py產生 test.py.lock 檔、放在腳本旁邊,未來uv run此腳本時若有找到此 lock 檔就會優先依照它的定義安裝依賴
-
指定此腳本需要的 Python 版本
- 直接更改開頭的「requires-python」
- 沒有
uv python pin功能
-
執行
Terminal window uv run test.py # 在獨立環境使用「requires-python」指定的 Python 版本和「dependencies」指定的依賴執行uv run --python 3.13 test.py # 指定用 Python 3.13 執行uv run --with numpy test.py # 加入「dependencies」未指定的 numpy 依賴執行- 每次
uv run時會自動確保使用正確的獨立環境與依賴,就沒有uv sync了
- 每次
安裝與使用全域工具套件
uv tool install ruff # 安裝 ruff 至全域,大約相當於 `npm add -g`、外加管理 Python 版本uv tool uninstall ruff # 從全域解除安裝 ruffuvx ruff # 一次性執行 ruff,大約相當於 `npx`、且於獨立的 Python 環境執行後記閒聊
原本只是在還沒裝過 Python 的新電腦上想安裝 Emscripten、要求得先裝 Python,於是想說聽過 uv 鼎鼎大名、藉此機會順便來學一下,沒想到就卡在這裡四小時……
順便記錄一下此次追查來龍去脈時用到的幾個好用指令:
- PowerShell
(Get-Command python).Source:查目前打python呼叫到的實際執行檔路徑- 不過若是
uv install --default這種情況、找到的只會是%USERPROFILE%\.local\bin底下的 Python.exe,沒辦法找到此執行檔裡再指向的下一個執行檔
- 不過若是
- PowerShell
where.exe python:查在目前的 PATH 裡,python所有呼叫候選執行檔- 實際打
python會呼叫到裡面的第一個
- 實際打
- cmd
dir:查看當前目錄下的所有……物件(?),重點是也可以看出那個 link 是 junction 還是 symbolic link
以及這次寫文時還遇到想在 code 裡面呈現反引號(`),學到是要改用更多反引號作為包括,比如我想呈現單個反引號、前後就用各兩個反引號來包住整段文字。
參考資料
- uv
- ChatGPT、Gemini
- uv 與 uvx 命令全攻略:Python 開發者的極速工具指南 | The Will Will Web