一. 前言:你執行的 Python,真的是你以為的那一個嗎? #
電腦裡只有一個 python 的日子,通常很短。
專案一多,環境很快就會變成這樣:
- macOS 或 Linux 自己附了一套 Python
- Homebrew、apt 或其他套件管理器又裝了一套
- 舊專案仍然需要 Python 3.11
- 新專案已經準備升到 Python 3.14
.venv裡還有另一個直譯器- CI 使用的版本和本機不完全相同
這時候最危險的不是「沒有 Python」,而是指令看似成功,實際上選錯直譯器。
你用
python --version看到 3.14,uv run python卻跑 3.12;你更新了系統 Python,舊.venv仍然連向另一個安裝位置。問題不在套件,而在 interpreter selection。 拍拍君今天只處理 uv 的 Python 版本管理:
- 分清楚 managed Python 與 system Python
- 安裝、列出並定位直譯器
- 用 request、pin 與專案契約控制選擇
- 安全升級 patch release
- 建立團隊與 CI 都能重現的診斷流程
如果你想學
uv add、uv sync、uv run與 lockfile,先看 uv 完全教學 或 uv workspace 實戰。如果你正在比較 venv、conda 與 uv 的定位,請看 Python 虛擬環境大比拼。
二. 安裝 uv,先確認目前狀態 #
macOS 與 Linux 可以使用官方 installer:
curl -LsSf https://astral.sh/uv/install.sh | sh
macOS 也可以用 Homebrew:
brew install uv
Windows PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
先確認版本:
uv --version
接著做三個不會改動環境的檢查:
uv python find
uv python list --only-installed
uv python dir
它們分別回答:
- uv 此刻會選到哪一個 Python?
- uv 能看到哪些已安裝版本?
- uv-managed Python 被放在哪裡?
這三個問題比直接猜
which python更完整,因為 uv 不只搜尋 shell 的PATH,也會考慮虛擬環境與自己管理的安裝。
三. Managed Python 與 System Python #
uv 把 Python 安裝分成兩類。
Managed Python #
由 uv 下載與安裝的版本,稱為 managed Python。
uv python install 3.13
uv 會使用可攜式的預建 Python distribution,把它放進 uv 的安裝目錄,並管理對應的 executable。
用下面指令查看實際位置:
uv python dir
uv python dir --bin
System Python #
不是 uv 安裝的版本,對 uv 而言都屬於 system Python。 這裡的「system」比作業系統內建更廣:
/usr/bin/python3- Homebrew 安裝的 Python
- 官方 installer 安裝的 Python
- pyenv 管理的 Python
- Windows registry 裡可發現的 Python
即使 Python 是 pyenv 安裝的,uv 仍把它歸類為 system Python,因為它不是 uv 自己管理的安裝。
這個分類很重要,因為
uv python upgrade只能升級 uv-managed Python,不能接管 Homebrew、pyenv 或作業系統的 Python。
四. 安裝版本:精確版、最新 Patch 與多版本 #
安裝某個 minor line 的最新 patch:
uv python install 3.12
安裝精確版本:
uv python install 3.12.12
一次安裝多個版本:
uv python install 3.11 3.12 3.13
也可以用範圍:
uv python install '>=3.11,<3.13'
或指定其他 implementation:
uv python install pypy@3.11
如果沒有帶版本,uv 會讀取目前的版本請求;沒有請求時則安裝最新可用版本:
uv python install
為什麼新版本可能找不到? #
uv 可下載的 Python 清單會隨 uv release 更新。剛發布的新 patch 或新 minor line 如果沒有出現在清單裡,先更新 uv,再重新查詢:
uv self update
uv python list 3.14
若 uv 是透過 Homebrew 或其他系統套件管理器安裝,請用原套件管理器更新,而不是混用更新方式。
五. Executable 與 PATH:為什麼裝完沒有 python?
#
預設安裝 Python 3.13 時,uv 會提供帶版本的 executable,例如:
python3.13
它不一定會覆蓋通用的 python 或 python3。這是保守而合理的行為,避免悄悄搶走系統既有指令。
先查看 executable 目錄:
uv python dir --bin
如果該目錄尚未進入 PATH:
uv python update-shell
重新開 shell,再檢查:
command -v python3.13
python3.13 --version
若你真的要讓 uv 提供 python 與 python3,目前可以使用 experimental --default:
uv python install 3.13 --default
團隊文件最好仍使用 uv run 或帶版本的 executable,不要假設每個人的 python 都指向同一處。
六. List:讀懂「已安裝」與「可下載」 #
最基本的查詢:
uv python list
輸出同時包含已安裝版本與可下載版本。要縮小範圍:
uv python list 3.13
uv python list pypy
uv python list --only-installed
只看 uv 管理的版本:
uv python list --managed-python
排除 uv-managed Python,只看 system Python:
uv python list --no-managed-python
診斷時別只截取版本號,也保留路徑:
cpython-3.14.x-macos-aarch64-none /opt/homebrew/bin/python3.14
cpython-3.12.x-macos-aarch64-none ~/.local/share/uv/python/.../python3.12
同樣是 CPython,來源、架構與維護者都可能不同。路徑才是定位問題的證據。
七. Find:問 uv 最後會選誰 #
list 是盤點,find 才是選擇結果:
uv python find
找特定 minor line:
uv python find 3.12
找符合範圍的版本:
uv python find '>=3.11,<3.14'
找特定 implementation:
uv python find pypy@3.11
也能直接請求 executable path:
uv venv --python /opt/homebrew/bin/python3.14
.venv 為什麼總是插隊?
#
如果目前目錄或父目錄存在 .venv,或 VIRTUAL_ENV 已設定,uv 通常會先考慮虛擬環境中的直譯器。
所以除錯時要一起檢查:
printf '%s\n' "${VIRTUAL_ENV:-<not set>}"
uv python find
uv run python -c 'import sys; print(sys.executable)'
想忽略虛擬環境並查 system interpreter:
uv python find --system
這比 which python 更能回答「uv 執行專案時會用誰」。
八. Python Request:不只可以寫 3.12
#
uv 接受多種版本請求。 常用格式如下:
| 請求 | 意義 |
|---|---|
3.12 |
任何相容的 CPython 3.12 patch |
3.12.12 |
精確 patch |
>=3.11,<3.14 |
版本範圍 |
cpython@3.13 |
指定 implementation 與版本 |
pypy@3.11 |
指定 PyPy |
3.13t |
free-threaded variant 的簡寫 |
/path/to/python |
指定本機 executable |
大多數 uv 指令都能用 --python 提出請求: |
uv venv --python 3.12
uv run --python 3.13 python -V
uv sync --python 3.12
請求不是「把所有地方永久改成 3.12」,而是告訴這次操作需要什麼 interpreter。要保存專案預設值,使用 pin。
九. Pin:把預設請求寫進專案 #
在專案根目錄執行:
uv python pin 3.12
uv 會建立 .python-version:
3.12
查看目前 pin:
uv python pin
通常應該把 .python-version commit 進 Git,讓開發者進入專案時得到相同的預設請求。
精確 Patch 還是 Minor Line? #
一般應用程式多半 pin minor line:
3.12
這樣可以取得同一 minor line 的安全 patch 更新。只有在重現特定 runtime bug、二進位相容性測試或嚴格驗證環境時,才考慮:
3.12.12
另外,雖然 uv 支援複雜 request,若 .python-version 也要讓 pyenv、mise 等工具讀取,單純版本號相容性最好。
Global Pin 要小心 #
uv 也支援:
uv python pin --global 3.13
它適合設定個人預設值,但不能取代每個專案的明確契約。團隊 repo 應該依賴 repo 內的設定,不要依賴某位開發者的 global pin。
十. .python-version 與 requires-python 不同
#
這兩個欄位常被混在一起,其實用途不同。
.python-version 是本機工具的 預設 interpreter request:
3.12
pyproject.toml 的 requires-python 是專案對使用者與 resolver 宣告的 支援範圍:
[project]
name = "pypy-tool"
version = "0.1.0"
requires-python = ">=3.11,<3.14"
一個合理組合可以是:
.python-version:3.12requires-python:>=3.11,<3.14- CI matrix:3.11、3.12、3.13
開發者平常用 3.12,套件仍承諾支援三個 minor line,CI 負責驗證承諾。
如果 pin 與
requires-python互相衝突,uv 不應該被迫猜測。修正設定,別用臨時 flag 掩蓋 repo 的契約錯誤。
十一. 自動下載:方便,但要知道怎麼關 #
uv 找不到符合條件的 Python 時,預設可以自動下載 managed Python。
例如:
uv venv --python 3.12
即使機器原本沒有 3.12,也可能直接完成。對新手與乾淨 CI runner 很方便,對離線環境或受控企業環境則可能太主動。
單次禁止下載:
uv --no-python-downloads venv --python 3.12
設定檔也能固定政策:
[tool.uv]
python-downloads = "never"
關閉自動下載後,缺少版本就應該明確失敗。這在 air-gapped CI、預先製作的 container image 與需要審核 runtime 來源的環境很有價值。
十二. Managed 還是 System:明確設定偏好 #
預設情況下,uv 可以同時發現 managed 與 system Python。需要更嚴格時可使用旗標。
只允許 uv-managed Python:
uv run --managed-python python -V
只允許 system Python:
uv run --no-managed-python python -V
常見政策:
- 個人開發機:偏好 managed,降低系統差異
- 公司基礎 image:只用預裝的 system Python
- CI:明確 install,再用
uv python find留下路徑證據 - 發行版維護:避免讓 uv 自動下載另一套 runtime 關鍵不是哪一類永遠比較好,而是不要讓選擇只存在於某台機器的 PATH 偶然順序裡。
十三. Upgrade:只升 Patch,不跨 Minor #
uv python upgrade 用來升級 uv-managed CPython 的 patch release,例如從 3.12.x 升到更新的 3.12.y。
目前這項功能仍是 preview,因此指令要明確開啟:
uv --preview python upgrade 3.12
升級全部 managed CPython:
uv --preview python upgrade
它不會自動把 3.12 變成 3.13,因為 minor upgrade 可能改變語法、ABI 與 dependency resolution。跨 minor line 應該是一次明確遷移:
uv python install 3.13
uv python pin 3.13
uv lock
uv sync
uv run pytest
升級 patch 後,舊安裝不會立刻被刪除,因為既有環境可能仍然使用它。新式 uv-managed 虛擬環境可以跟隨 minor-version link 升級;但精確 pin patch、舊環境或被其他工具 canonicalize 過的路徑可能不會透明更新。 因此升級後一定要驗證:
uv python find 3.12
uv run python -c 'import platform, sys; print(platform.python_version()); print(sys.executable)'
uv run pytest
十四. 團隊工作流:把版本變更當成 PR #
一個乾淨的 repository 可以同時保存:
project/
├── .python-version
├── pyproject.toml
├── uv.lock
├── src/
└── tests/
升級 minor line 時,不要只改 .python-version。建議在同一個 PR 完成:
- 修改
.python-version - 檢查
requires-python - 更新 CI matrix
- 重新產生或驗證
uv.lock - 執行測試、lint 與 type check
- 記錄不相容 dependency 或 runtime 行為 最小診斷 script:
from __future__ import annotations
import json
import platform
import sys
report = {
"version": platform.python_version(),
"implementation": platform.python_implementation(),
"executable": sys.executable,
"prefix": sys.prefix,
"base_prefix": sys.base_prefix,
"platform": platform.platform(),
}
print(json.dumps(report, indent=2, ensure_ascii=False))
用 uv 執行:
uv run python scripts/runtime_report.py
Bug report 附上這份輸出,通常比一句「我用 Python 3.12」有效得多。
十五. 常見問題與排查順序 #
問題一:uv python find 找到 .venv,不是剛安裝的版本
#
先確認目前是否位於舊專案或已啟用環境:
pwd
printf '%s\n' "${VIRTUAL_ENV:-<not set>}"
uv python find
uv python find --system
需要重建環境時,先確認沒有未保存的本機依賴資訊,再移除並執行 uv sync。不要只改 symlink 讓環境看似更新。
問題二:裝了 Python,shell 仍找不到 #
uv python dir --bin
uv python update-shell
重新開 shell,再用 command -v python3.x 確認。shell 可能快取舊位置,必要時執行 hash -r。
問題三:同版本卻裝不了 wheel #
檢查 architecture 與 implementation:
uv run python -c 'import platform; print(platform.machine(), platform.python_implementation())'
Apple Silicon 上的 arm64 與經 Rosetta 執行的 x86_64 不能混用 native wheel。版本號相同,不代表平台 tag 相同。
問題四:upgrade 沒有跨到下一個 minor line
#
這是設計,不是 bug。upgrade 只升 patch;跨 minor 要 install、pin、resolve、test。
問題五:想清理舊版本 #
先盤點:
uv python list --only-installed
確認沒有專案與虛擬環境依賴後,再移除指定的 managed Python:
uv python uninstall 3.11
uv 不會替你移除 Homebrew、pyenv 或 OS 管理的 system Python;請回到原安裝工具處理。
結語 #
uv 管理 Python 版本的價值,不只是少裝一個 pyenv。 真正有用的是,它把「Python 在哪裡、從哪裡來、為什麼被選中、專案需要哪個版本」變成可以查詢與 commit 的資訊。 記住這組分工:
uv python install:讓版本存在uv python list:盤點可用與已裝版本uv python find:確認實際選擇uv python pin:保存專案預設 requestrequires-python:宣告專案支援範圍uv --preview python upgrade:升級 managed CPython patch 下次遇到「明明都是 Python 3.12,為什麼只有這台會壞」,先不要重裝整個世界。請 uv 把 interpreter 的身分與路徑說清楚,通常真相就出現了。🔎