快轉到主要內容
  1. 教學文章/

Python uv 進階:workspace、lockfile、script 與專案管理完全攻略

·8 分鐘· loading · loading · ·
Python Uv Workspace Lockfile Packaging Dev Tools
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 51: 本文

featured

一、前言
#

如果你已經用過 uv inituv adduv run,應該會有一種感覺:「哇,這東西真的很快。」 但用了一陣子之後,新的問題也會冒出來:專案變大怎麼辦?uv.lock 要不要 commit?CI 裡該跑哪個指令?一次性的 Python 小工具要不要也開專案?monorepo 裡的 API、CLI、共用 library 要怎麼一起管理? 上一篇 Python uv:超快 Python 套件管理工具 偏入門,今天這篇要往工程化方向走:workspace、lockfile、script、團隊協作與專案維護策略。 換句話說,今天不是「uv 怎麼開始用」,而是「uv 怎麼用得穩、用得久、用得像正式專案」。

二、先釐清:uv 的三種使用模式
#

uv 可以粗略分成三種使用模式:project、script、pip 相容模式。 這三種不是互斥,而是各自適合不同情境。

當你有一個資料夾,裡面有 pyproject.toml、原始碼、測試、README,這就是 project 模式。

uv init my-app
cd my-app
uv add httpx rich
uv run python main.py
uv sync

uv 會幫你維護 .venv/pyproject.tomluv.lock。 大多數應用程式、套件、CLI 工具,都建議從 project 模式開始。

script 模式:一次性工具超好用
#

有時候你只想寫一個小工具:抓 API 報表、清理資料夾、轉換 CSV、檢查網站狀態。 以前你可能會為了這種小工具建立整個專案,或在 README 裡寫「請先 pip install 這三個套件」。 uv 的 script 模式可以把依賴直接寫在 .py 檔裡,等等會示範。

pip 相容模式:遷移期好朋友
#

如果舊專案仍然使用 requirements.txt,可以先不大改架構:

uv pip install -r requirements.txt

新專案盡量用 project 模式;舊專案先用 uv pip 加速,再逐步搬到 pyproject.toml + uv.lock

三、建立一個乾淨的 uv 專案
#

先從最基本但正確的專案開始。

uv init daily-tools
cd daily-tools

預設會產生類似這樣的結構:

daily-tools/
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

pyproject.toml 大概會長這樣:

[project]
name = "daily-tools"
version = "0.1.0"
description = "A tiny tool collection"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

接著加入依賴:

uv add httpx rich typer

uv 會更新 pyproject.toml、解出依賴樹、產生或更新 uv.lock,並同步 .venv/。 這裡有一個很重要的觀念:pyproject.toml 是你想要什麼,uv.lock 是實際解出什麼。 例如 pyproject.toml 可能寫:

[project]
dependencies = [
    "httpx>=0.28.0",
    "rich>=13.9.0",
    "typer>=0.15.0",
]

這代表你接受某個版本範圍。 uv.lock 則會記錄完整版本,包括 httpxhttpcoreanyiocertifi 等間接依賴。 拍拍君通常建議:application、CLI、部署服務都要 commit uv.lock;library 可以看團隊策略,但測試用 lockfile 也很有幫助。

四、uv sync:讓環境回到 lockfile 狀態
#

uv add 很方便,但團隊協作時更常用的是:

uv sync

它的意思是:根據 pyproject.tomluv.lock,把目前環境同步到應該有的狀態。 剛 clone 一個 repo 時,典型流程是:

git clone https://example.com/daily-tools.git
cd daily-tools
uv sync
uv run pytest

不用手動建立 venv,不用 activate,也不用猜 Python 版本。 uv 會讀 .python-versionrequires-python,必要時甚至能幫你下載 Python。

CI 裡建議使用 –locked
#

在 CI 裡,拍拍君不喜歡讓 lockfile 被默默改掉。

uv sync --locked
uv run pytest

--locked 的意思是:如果 pyproject.tomluv.lock 不一致,就直接失敗。 這能抓出「某人改了 dependencies,但忘記更新 lockfile」這種問題。

部署環境常用 –frozen
#

部署或容器 build 時,通常希望直接照 lockfile 安裝,不重新解析。

uv sync --frozen

簡化記法如下:

指令 適合場景 行為重點
uv sync 本機開發 可以解析並更新 lockfile
uv sync --locked CI 檢查 lockfile 必須已經是最新
uv sync --frozen 部署、容器 不重新解析,直接照 lockfile 安裝
口訣:本機 uv sync,CI uv sync --locked,部署 uv sync --frozen

五、uv run:不用 activate 的日常工作流
#

傳統 Python 工作流常常是:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python main.py
pytest

uv 的思路比較像:

uv sync
uv run python main.py
uv run pytest

你不一定需要 activate。 uv run 會確保指令在專案環境裡執行。

uv run python --version
uv run python main.py
uv run pytest
uv run ruff check .
uv run typer daily_tools.main utils docs

這對新手和 CI 都很友善,因為你不用擔心現在 shell 到底啟用了哪個環境。

臨時加套件:–with
#

有些情況你想暫時使用某個工具,但不想把它加入專案依賴。

uv run --with rich python -c "from rich import print; print('[green]hello uv[/green]')"

或臨時開一個 HTTP client 測試:

uv run --with httpx python - <<'PY'
import httpx
r = httpx.get('https://example.com')
print(r.status_code)
PY

如果只是探索、debug、一次性操作,--with 很好用。 但如果某個套件會長期被專案使用,請 uv add,不要一直藏在 shell history 裡。

六、管理 dev dependencies
#

正式專案通常會分成 runtime dependencies 和 development dependencies。 程式執行時需要的套件,例如 httpxtyperpydantic,放在一般 dependencies。 測試、lint、type check 需要的套件,例如 pytestruffmypy,放在 dev group。

uv add httpx typer pydantic
uv add --dev pytest ruff mypy pre-commit

pyproject.toml 會出現類似:

[dependency-groups]
dev = [
    "mypy>=1.15.0",
    "pre-commit>=4.0.0",
    "pytest>=8.3.0",
    "ruff>=0.9.0",
]

平常開發:

uv sync

部署環境只裝 runtime dependencies:

uv sync --no-dev

CI 通常會安裝 dev dependencies,因為要跑測試與檢查:

uv sync --locked
uv run ruff check .
uv run mypy src
uv run pytest

這樣專案依賴就很清楚:誰是產品需要,誰只是開發工具。

七、workspace:monorepo 的正式解法
#

如果 repo 裡只有一個 package,普通 project 模式就夠了。 但如果你有 API、CLI、共用 library,workspace 就很有用。

daily-platform/
├── pyproject.toml
├── uv.lock
└── packages/
    ├── daily-core/
    ├── daily-api/
    └── daily-cli/

workspace 讓多個 Python package 放在同一個 repo 裡,共用一份 lockfile,一起開發、一起測試。 根目錄 pyproject.toml 可以這樣設定:

[project]
name = "daily-platform"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

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

假設 daily-api 依賴同 repo 裡的 daily-core,重點是這段:

[project]
name = "daily-api"
dependencies = ["daily-core", "fastapi>=0.115.0", "uvicorn>=0.34.0"]

[tool.uv.sources]
daily-core = { workspace = true }

daily-core = { workspace = true } 告訴 uv:不要去 PyPI 找 daily-core,請使用同一個 workspace 裡的成員。 CLI package 也可以用同樣方式依賴 daily-core

[project]
name = "daily-cli"
dependencies = ["daily-core", "typer>=0.15.0", "rich>=13.9.0"]

[project.scripts]
daily = "daily_cli.main:app"

[tool.uv.sources]
daily-core = { workspace = true }

這樣你改 daily-core,API 和 CLI 都會立刻用到本機版本,不需要先發佈到 PyPI,也不需要手動 pip install -e

八、workspace 常用指令
#

同步全部 package:

uv sync --all-packages

在特定 package 裡跑指令:

uv run --package daily-cli daily --help
uv run --package daily-api uvicorn daily_api.app:app --reload

幫特定 package 加依賴:

uv add --package daily-api httpx
uv add --package daily-cli --dev pytest

只同步某個 package:

uv sync --package daily-api

拍拍君建議:一開始先用根目錄 CI 跑全部測試;等 workspace 真的變大,再做 package-level 最佳化。

九、lockfile 的團隊規則
#

uv.lock 很好用,但團隊需要約定規則,不然很容易出現「誰改了 lockfile?」的混亂。 拍拍君推薦這套規則:

  1. 改依賴用 uv add / uv remove
  2. 更新既有依賴用 uv lock --upgrade-package <name>
  3. 不要手改 uv.lock
  4. application repo commit uv.lock
  5. CI 使用 uv sync --locked
  6. PR 裡如果有 pyproject.toml 變更,通常也要有 uv.lock 變更 只更新單一套件:
uv lock --upgrade-package httpx
uv sync
uv run pytest

全部升級:

uv lock --upgrade
uv sync
uv run pytest

依賴升級最好獨立成 PR,不要跟功能改動混在一起。 不然壞掉時,你很難判斷是新功能出問題,還是依賴升級出問題。

十、inline script metadata:小工具不用開專案
#

假設你有一個檔案 tools/check_status.py

# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx>=0.28.0", "rich>=13.9.0"]
# ///
import httpx
from rich.console import Console

response = httpx.get("https://example.com", timeout=10)
Console().print({"status_code": response.status_code})

直接跑:

uv run tools/check_status.py

你也可以用指令管理腳本依賴:

uv add --script tools/check_status.py httpx rich
uv remove --script tools/check_status.py rich

script 模式適合 repo 維護腳本、demo、一次性資料轉換、小型自動化、教學範例。 如果它是一個「檔案」就能講完,用 script;如果它是一個「專案」,用 project。

十一、從 requirements.txt 遷移到 uv
#

很多既有專案不會一開始就有 pyproject.toml。 如果你現在有 requirements.txtrequirements-dev.txt,可以分階段遷移。 第一階段,先用 uv pip 加速,幾乎不改專案結構:

uv venv
uv pip install -r requirements.txt
uv pip install -r requirements-dev.txt

第二階段,建立 pyproject.toml 並導入 lockfile:

uv init --bare
uv add -r requirements.txt
uv add --dev -r requirements-dev.txt
uv lock
uv sync
uv run pytest

第三階段,更新 CI:

- run: pipx install uv
- run: uv sync --locked
- run: uv run pytest

重點不是一天內全部搬完,而是每一步都能驗證。

十二、常見踩雷與推薦流程
#

第一個坑:忘記 commit uv.lock。 如果是 application,這通常是錯的,因為同事或 CI 可能解析出不同版本。

git add pyproject.toml uv.lock

第二個坑:手動改 lockfile。 不要。uv.lock 是工具產物,不是給人手寫的。 第三個坑:把 dev tools 放進 runtime dependencies。 pytestruffmypy 應該用:

uv add --dev pytest ruff mypy

第四個坑:workspace package 忘記設定 source。

[tool.uv.sources]
daily-core = { workspace = true }

第五個坑:CI 沒有檢查 lockfile。

uv sync --locked

如果你要開一個新的 Python CLI 或服務,拍拍君會這樣做:

uv init daily-app
cd daily-app
uv python pin 3.12
uv add typer rich pydantic
uv add --dev pytest ruff mypy
uv sync
uv run pytest

這套流程很輕,但已經足夠支撐很多正式專案。 等專案真的長大,再考慮拆 workspace;不要一開始就把架構弄得像巨獸。

十三、結語
#

uv 最吸引人的地方一開始是速度,但真正讓它值得長期使用的,是它把 Python 專案管理變得很一致。 今天拍拍君帶你看了 uv syncuv run、dev dependencies、workspace、uv.lock、inline script metadata、requirements 遷移,以及 Docker/CI 裡的實務用法。 如果只記一件事,拍拍君希望你記得這句:uv 不只是比較快的 pip,它是一套現代 Python 專案工作流。 小專案可以從 uv init 開始;中型專案加上 uv.lock、CI 與 dev groups;大型 monorepo 再導入 workspace。 不要為了進階而進階,但也不要讓舊工具鏈拖慢你。 讓環境管理安靜下來,你就能把腦力留給真正重要的程式碼。拍拍君覺得,這才是好工具最可愛的地方。

延伸閱讀
#

Python 學習 - 本文屬於一個選集。
§ 51: 本文

相關文章

Python virtual environments 大比拼:venv vs conda vs uv 怎麼選?
·8 分鐘· loading · loading
Python Venv Conda Uv Virtual Environments Packaging
Python Packaging:從 pyproject.toml 到發佈 PyPI 全攻略
·7 分鐘· loading · loading
Python Packaging Pyproject.toml Pypi Pip Uv Setuptools
超快速 Python 套件管理:uv 完全教學
·6 分鐘· loading · loading
Python Uv Package Manager Rust
Python argparse 實戰:CLI 參數解析、旗標設計與 subcommands 完全攻略
·9 分鐘· loading · loading
Python Argparse Cli Command-Line Automation Developer-Tools
Python ABC + Protocol:介面設計與 Structural Subtyping
·8 分鐘· loading · loading
Python Abc Protocol Typing Interface Oop
Python Generators/Yield 完全攻略:惰性運算的藝術
·7 分鐘· loading · loading
Python Generator Yield Lazy Evaluation Iterator