2273 字
11 分鐘
【Python】透過 uv 安裝與管理 Python 版本、環境、專案與套件

  uv 取代了之前的 pip、還加上 Python 本體的版本管理,概念上大概像是 Node.js 生態系的 fnm 加 pnpm;簡單試了一下新專案、感覺執行速度也不錯,真是時代在進步啊……希望趕快推廣開來。

在 Windows 上安裝 uv#

  官方的安裝說明文件在此:Installation | uv

  雖然官方最推薦的是直接用 PowerShell 的 irm | iex 執行,不過若是比較擔心資安問題、也可以透過 WinGet 安裝,至少有多一層 Microsoft 的認證機制、避免中間人攻擊。

Terminal window
winget install --id=astral-sh.uv -e
uv --version # 顯示版本以驗證安裝成功

  而且如果用 PowerShell irm | iex 安裝,裝好後還要再多一步驟把它安裝到的路徑 %USERPROFILE%\.local\bin 加入環境變數 PATH;而透過 WinGet 安裝會裝在 WinGet 已經設好的 PATH 路徑下、免去這一步驟。

透過 uv 安裝 Python#

Terminal window
uv python list # 查看所有可安裝與已安裝的版本
uv python install # 安裝最新的穩定版本
uv python install 3.13 # 安裝指定版本

安裝為全域 Python#

  上一步安裝的 Python 只是 uv 內部使用,不能在任意目錄呼叫;如果想把透過 uv 安裝的 Python 變成任意目錄可呼叫,在我目前使用的 uv 版本 0.11.8 要再加上兩組參數變成這樣(未來此功能進正式版後就不用加 preview-features 參數了):

Terminal window
uv python install --default --preview-features python-install-default # 安裝最新的穩定版本、並設為全域
uv python install 3.13 --default --preview-features python-install-default # 安裝指定版本、並設為全域
python --version # 顯示版本以驗證

實際細節#

  我在這串嘗試過程中發現了一點端倪,追查下去之後終於釐清了實際執行細節的來龍去脈,在這裡展開聊一下:

  1. 首先執行 uv python dir 可以查到 uv 管理的 Python 實際安裝的目錄,以下用 <UV_PYTHON_INSTALL_DIR> 代稱(實際也確實可以用 UV_PYTHON_INSTALL_DIR 這個環境變數作為 uv 管理 Python 目錄的設定)
  2. 執行 uv python install 3.13 時,實際是找到該版號下最新穩定版「3.13.13」後、安裝在 <UV_PYTHON_INSTALL_DIR>\cpython-3.13.13-windows-x86_64-none 目錄下,然後在旁邊建立一個 junction cpython-3.13-windows-x86_64-none、以讓 uv 未來要找 3.13 版時順著這個 junction 找到 3.13.13 實際 Python 執行檔。
  3. 執行 uv python install 3.13 --default 時,實際上是做了兩件事:
    1. %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-none junction、「python.exe」和「python3.exe」指向「python3.13.exe」
    2. 檢查目前環境變數 PATH 裡是否有 %USERPROFILE%\.local\bin,如果沒有、就在使用者環境變數 PATH 的最前面加上去
      • 不過新加的環境變數 PATH 要在未來開啟的 terminal 才會生效,所以當下會看到 uv 警告說 `%USERPROFILE%\.local\bin` is not on your PATH.
    • 靠著這兩個行為,未來開啟的 terminal 裡直接呼叫 pythonpython3 時,就可以沿線找到 uv 管理的 3.13.13 版本來執行了。
  4. 若是接著再執行 uv python install --default、而此時的最新版本是 3.14.4,而且之前已經執行過 uv python install、所以其實已經安裝好 <UV_PYTHON_INSTALL_DIR>\cpython-3.14.4-windows-x86_64-none,實際則是做了以下的事:
    1. 找到最新穩定版「3.14.4」,然後發現 uv 管理的 Python 裡已經有此版本了、旁邊也已有 3.14 版的 junction,就不再執行安裝
    2. %USERPROFILE%\.local\bin 產生一個「Python3.14.exe」、指向 <UV_PYTHON_INSTALL_DIR>\cpython-3.14-windows-x86_64-none junction
    3. %USERPROFILE%\.local\bin 產生「Python.exe」與「Python3.exe」替代掉舊的,內容是指向「Python3.14.exe」
    4. 檢查目前環境變數 PATH、發現已有 %USERPROFILE%\.local\bin,不再動作
    • 這樣沒有改到環境變數 PATH、所以現有 terminal 也直接生效,呼叫 pythonpython3python3.14 會沿線找到 uv 管理的 3.14.4 版本來執行,而呼叫 python3.13 還是會呼叫到上一步產生的「Python3.13.exe」、沿線找到的也就還是 uv 管理的 3.13.13 版本。

使用 uv 管理專案#

  1. 初始化

    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
  2. 切換此專案的 Python 版本

    Terminal window
    uv python pin 3.13
    • 會將 .python-version 內容改為指定的版本
    • 如果指定的版本超出 .pyproject.toml 指定的 requires-python 範圍,會被擋下來
  3. 管理依賴

    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
  4. 接手專案後在本地安裝依賴

    Terminal window
    uv sync # 安裝正式與開發依賴,不含其他 group
    uv sync --no-dev # 只安裝正式依賴
    uv sync --group lint # 只安裝「lint」group 的依賴
    uv sync --all-group # 安裝所有依賴
  5. 執行

    Terminal window
    uv run main.py
    • 會用專案內的 venv 執行,其中這個 venv 的 Python 版本由 .python-version 指定、包含的 package 由 pyproject.toml 與 uv.lock 指定

使用 uv 管理單一 py 檔腳本#

  1. 初始化

    Terminal window
    uv init .\test.py --script # 建立 test.py py 檔、初始化為 Python 腳本
    • 只會建立 test.py 單一 py 檔,開頭會有以 # /// 包裹的一段註解(如下)、是此腳本的 Python 版本與依賴資訊
      # /// script
      # requires-python = ">=3.14"
      # dependencies = []
      # ///
      • 這也不是 uv 專屬、而是許多工具支援的格式。
  2. 管理依賴

    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 檔就會優先依照它的定義安裝依賴
  3. 指定此腳本需要的 Python 版本

    • 直接更改開頭的「requires-python」
    • 沒有 uv python pin 功能
  4. 執行

    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

安裝與使用全域工具套件#

Terminal window
uv tool install ruff # 安裝 ruff 至全域,大約相當於 `npm add -g`、外加管理 Python 版本
uv tool uninstall ruff # 從全域解除安裝 ruff
uvx ruff # 一次性執行 ruff,大約相當於 `npx`、且於獨立的 Python 環境執行

後記閒聊#

  原本只是在還沒裝過 Python 的新電腦上想安裝 Emscripten、要求得先裝 Python,於是想說聽過 uv 鼎鼎大名、藉此機會順便來學一下,沒想到就卡在這裡四小時……

  順便記錄一下此次追查來龍去脈時用到的幾個好用指令:

  1. PowerShell (Get-Command python).Source:查目前打 python 呼叫到的實際執行檔路徑
    • 不過若是 uv install --default 這種情況、找到的只會是 %USERPROFILE%\.local\bin 底下的 Python.exe,沒辦法找到此執行檔裡再指向的下一個執行檔
  2. PowerShell where.exe python:查在目前的 PATH 裡,python 所有呼叫候選執行檔
    • 實際打 python 會呼叫到裡面的第一個
  3. cmd dir:查看當前目錄下的所有……物件(?),重點是也可以看出那個 link 是 junction 還是 symbolic link

  以及這次寫文時還遇到想在 code 裡面呈現反引號(`),學到是要改用更多反引號作為包括,比如我想呈現單個反引號、前後就用各兩個反引號來包住整段文字。

參考資料#

延伸閱讀#