文章目錄
- 安裝與更新
- standalone
- pip 安裝
- 創建以及初始化項目
- 依賴管理
- uv run
- 直接在命令行運行python代碼片段
- 直接運行項目中可執行腳本文件
- 運行python包中快捷指令
- uv項目本地運行調試細節
- vscode 中運行調試uv項目
- 命令行運行
- 深入理解 uv lock, uv sync, uv lock
- uv lock 行為解析:
- uv sync 行為解析
- uv run 行為解析
- uv項目的docker file
uv是一個更現代的python項目管理器.筆者在使用uv前,筆者都是使用anaconda指令(比如
conda create -n xxx python=3.11 -y
) 為每個項目創建隔離的虛擬環境,然后使用
conda activate xxx
激活此環境,最后使用
pip install -r requirements.txt
這種方式來安裝或者更新項目依賴.而有了uv之后可以讓python項目管理變得更加的規范.本文著重講解筆者最近切換到使用uv進行python項目管理過程中uv的一些常規用法和技巧,后續有用到新的feature會同步更新本文.
安裝與更新
這里只講兩種筆者認為最多涉及到的安裝與更新方式,分別是命令行standard安裝更新和pip安裝更新
standalone
本地開發機器上安裝以及更新的方式,筆者是ubuntu系統,所以安裝使用如下指令
curl -LsSf https://astral.sh/uv/install.sh | sh
參考官網uv安裝
更新的指令即是:
UV_NO_MODIFY_PATH=1 uv self update
pip 安裝
pip 安裝適合在構建docker鏡像
的時候選擇的安裝方式,因為一般構建docker鏡像選擇的基礎鏡像都是一個最小化的python basic image
.它可能只包含基本的python環境和pip,所以在Dockerfile
中使用pip安裝uv,安裝指令如下:
pip install uv
既然是pip安裝的包,更新指令就是
pip install --upgrade uv
不過這個更新指令基本使用不到,構建鏡像過程中只需要在基礎鏡像安裝uv,不需要更新uv.
創建以及初始化項目
關鍵指令為
uv init [OPTIONS] [PATH]
比如在python-uv
目錄下執行uv init --python=3.11 test
命令會創建一個名字為test的子目錄
且子目錄作為項目根目錄,命令指定了項目運行所需的python版本為3.11
,同時在test
目錄下創建項目配置文件pyproject.toml
,配置文件中設置項目名稱為test
,和項目根目錄同名,同時還有一些git repo必需的文件以及文件夾,可見init指令也將項目初始化成一個git repo.先看一下目錄結構:
test├── .git├── .gitignore├── main.py├── pyproject.toml├── .python-version└── README.md
如果init指令不指定參數PATH
則是將運行uv init所在目錄
作為項目根目錄去創建上面這些內容.此時在項目內運行uv sync
才能看到虛擬環境目錄.venv
和uv.lock
被創建.所以完整的項目初始化之后結構如下:
└── test├── .git├── .gitignore├── main.py├── pyproject.toml├── .python-version├── README.md├── uv.lock└── .venv
有幾個重要文件和文件夾需要提前說明:
- .venv: 項目python虛擬環境和依賴包相關文件夾. 可以在項目目錄上下文執行指令
source .venv/bin/activate
激活虛擬環境.但是由于使用uv來構建以及管理的項目使用uv指令居多,所以一般情況下不需要顯式的激活這個虛擬環境.同時.venv
也不應該上傳到版本控制系統,每一次項目repo同步下來只需要運行uv sync
則可以更新本地的虛擬環境中lib更新到最新,繼續開發項目或者本地調試. - pyproject.toml: 存放整個項目的元數據,視作項目配置文件,其內部包含項目名稱,描述,依賴,構建,腳本以及工具等等,一般執行uv相關命令都會涉及到更改此配置文件,官方關于此配置文件編寫教程鏈接pyproject.如果從遠端同步下來uv項目,可以執行
uv sync
即是根據此配置文件去構建本地的開發虛擬環境和依賴包. - uv.lock 文件:項目依賴的精確版本信息,和
pyproject.toml
配置不同,配置文件一般是描述依賴的版本邊界約束
,比如某個依賴包最低版本或者最高版本.lock文件中是真正虛擬環境內安裝的精確版本,任何涉及到更新依賴的命令比如uv sync
,uv add {package}
等操作在依賴的版本邊界約束
內可能拉取最新的版本,就會更新此lock文件.此文件真正起作用的地方是在CI構建過程,因為虛擬環境文件不會提交到版本管理系統,每次構建都需要拉取這些依賴,涉及到使用指令uv sync
.由于前面所說這個指令可能會拉取新的依賴版本可能引入不兼容問題,因此使用指令uv sync --frozen
強制使用uv.lock文件中的精確版本來完成構建過程,保證構建前后的版本一致,降低構建過程引入新版本帶來的風險.uv.lock文件要求必須上傳到版本控制系統且不可以手動更改.
這里可以看到init
甚至可以將項目初始化成一個git repo,實際可能不需要這個功能,多半情況下本地已經有git repo了只需要創建以及初始化uv 項目即可,這個情況下,可以稍微調整一下init指令相關的optional參數關閉git repo相關feature即可.另外--name
也可以顯示指定項目的名稱,這樣就不是默認的以項目文件夾名字作為項目名稱了,刪除當前的test文件夾,使用如下命令創建新的非git repo的uv項目:
uv init test --description="a test uv project" --vcs=none --no-readme --python=3.11 --managed-python
上面指令還會生成一個main.py
的入口文件,然后在test
目錄下執行如下命令可以自動創建虛擬環境
uv run main.py
輸出:
Using CPython 3.11.13
Creating virtual environment at: .venv
Hello from test!
可以看到虛擬環境文件夾.venv
和uv.lock
文件都已經創建好了,至此項目初始化結束.
這里簡要說明一下剛創建好的項目是沒有虛擬環境文件夾.venv
的,除了使用uv run main.py
之外還可以使用uv sync
命令.本質上uv會查看本地是否有項目指定運行的python版本,比如本項目初始化的時候指定的是3.11版本的python,沒有的話會先下載此版本python到~/.local/share/uv/python
目錄,然后再將此版本python"拷貝"到當前項目.venv
文件夾中.可以在項目文件夾為上下文的命令行窗口中執行指令source .venv/bin/activate
來激活此(使用deactivate
則退出)虛擬環境.只是使用uv指令的時候是完全不需要顯式激活虛擬環境這個操作.
依賴管理
uv 添加依賴有uv add
命令,它會將依賴包安裝到當前虛擬環境.venv
中同時更新pyproject.toml
中的dependencies配置. 比如需要把最新的fastapi
添加到項目中可以使用uv add "fastapi[standard]==0.116"
添加一個固定版本的fastapi
依賴包.當依賴安裝好后查看pyproject.toml
可以看到依賴已被添加
dependencies = ["fastapi[standard]==0.116.0",
]
實際開發過程中不會這么嚴格限制一個版本,需要支持能夠獲取最新的bugfix修訂版本,所以一般依賴都會給定一個版本范圍比如fastapi
版本是0.116到0.117版本的最新修訂版本
,那么添加依賴指令變為uv add "fastapi[standard]>=0.116,<0.117"
,此命令會覆蓋pyproject.toml
中的版本約束,同時升級虛擬環境中安裝的fastapi到0.116.x最新版本修. 當然也可以直接修改pyproject.toml
里面的fastapi依賴,如下
dependencies = ["fastapi[standard]>=0.116,<0.117",
]
然后再運行uv sync
也能達到同樣效果.但是最佳做法還是使用uv add
增加或者修改現有的依賴.
刪除依賴則是uv remove
指令.
默認情況下如果安裝依賴不指定版本約束,當前會安裝最新版本,且dependencies會寫入 dep >= latest version
,比如執行如下指令
uv add httpx
查看dependencies
dependencies = ["httpx>=0.28.1",
]
此時安裝的是最新版本,當讓可以修改依賴的版本約束uv add "httpx>=0.28,<0.29"
限制依賴版本為0.28.x
的最新修訂版本.
uv中的依賴主要是三類,第一類是project.dependencies
項目依賴,默認情況下使用指令uv add xxx
的依賴都屬于項目依賴,這些依賴在配置文件里面回添加到[project]
配置段下的dependencies
里面.這些依賴說白了都是代碼中引入的包,代碼運行時必不可少的包.
第二種是dependency groups.就是開發所需依賴,不會被大包到項目中去,所以這類依賴也不會出現在[project]
配置段中,只會出現在[dependency-groups]
配置段中.
有兩種使用dependency groups的方法,第一種是uv add --dev xxx
把依賴放入dev這個group,可以理解為dev是內置的dependency groups.比如將pytest
這個包放入dev
group用于項目測試.
uv add pytest --dev
配置文件
[dependency-groups]
dev = ["pytest>=8.4.1",
]
當然要刪除在dev group中的這個包也需要加上–dev flag uv remove pytest --dev
. 注意dev
這個group在uv sync
的時候也會拉取相應的依賴包.
除了dev
這個官方定義的組之外,還可以自定義組,使用指令uv add --group {group_name} {package}
實現,比如:
uv add --group lint ruff
會創建一個lint
的自定義組,且添加ruff依賴到此組.
[dependency-groups]
dev = ["pytest>=8.4.1",
]
lint = ["ruff>=0.12.8",
]
刪除的話也需要添加flaguv remove ruff --group lint
自定義組在使用uv sync
時是不會拉取依賴的,需要配置[tool.uv]
下面的default-groups
追加自定義的group
[tool.uv]
default-groups = ["dev", "lint"]
uv run
uv run 指令是非常強大的指令. 下面全面說明它的用法:
直接在命令行運行python代碼片段
它可以在命令行運行一段python代碼片段,比如:
uv run python -c "import sys;print(sys.executable)"
輸出當前解釋器的位置~/.local/share/uv/python/cpython-3.11.13-linux-x86_64-gnu/bin/python3.11
直接運行項目中可執行腳本文件
它還可以直接運行項目中的可執行腳本文件,比如項目中存在如下python腳本文件
#!/usr/bin/env python
#-*- coding:utf-8 -*-import httpxif __name__ == '__main__':print(httpx.__version__)pass
輸出結果:
0.28.1
這里用到了項目依賴httpx
,但是可以在不需要顯式激活當前虛擬環境
的情況下直接打印當前虛擬環境中的依賴包信息.
除了python腳本外,uv run還可以直接運行shell腳本,比如有如下foo.sh的shell腳本:
python cli.py
可以使用uv run bash foo.sh
也能正常運行腳本獲取打印的依賴包版本信息.
再看一個細節,比如下面沒有shebang行的python腳本sample.py
if __name__ == '__main__':print('hello')
如果直接命令行運行./sample.py
肯定報錯,大家都知道需要指定python解釋器運行,比如python sample.py
使用默認的解釋器運行就不會報錯.使用uv 運行此腳本則是命令uv run sample.py
也能正確輸出結果.uv運行python腳本的整個過程可以等價于如下操作:
uv sync
source .venv/bin/activate
python sample.py
當我們知道當前要運行的腳本是python時候,甚至可以直接用指令uv run -- python sample.py
運行python腳本.
運行python包中快捷指令
我們知道有些包安裝后是有在命令行運行的快捷指令的,比如環境中安裝了fastapi[standard]
,那么在命令行里面就可以使用指令fastapi dev xxx.py
以develop模式快速啟動fastapi web app. 那么在uv管理的項目里面依然可以運行這種命令行指令.比如先在uv項目里面添加fastapi[standard]
包
uv add "fastapi[standard]"
編寫一個fastapi web app 的 main.py文件:
#-*- coding:utf-8 -*-from fastapi import FastAPIapp = FastAPI(title='test')@app.get("/")
async def index():return 'hello'
uv 命令 develop 模式啟動此app:
uv run -- fastapi dev main.py --port 8090
這里 uv run --
中的--
保證此字符后面都是fastapi的命令行指令與其運行參數,而不會被解析成uv run
參數,非常關鍵。
curl測試以及輸出結果:
curl http://localhost:8090
"hello"
因此基本上使用uv在命令行運行這些包中的快捷指令
基本就是 uv run -- {package cli cmd} {cli params}
這樣的形式.
uv項目本地運行調試細節
上一部分完全探討了uv run這個指令,這一部分詳細探討uv項目本地運行以及調試的細節.
這里涉及到兩種運行:1. vscode 中運行調試.2. 本地命令行中調試運行細節
vscode 中運行調試uv項目
和之前筆者用conda創建虛擬環境,在vscode中調試項目那一套基本操作類似.這里首先需要創建項目vscode debug所需的launch.json文件,文件最基本內容如下,可以根據需求進行更改.
{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "uv test single file","type": "debugpy","request": "launch","program": "${file}","console": "integratedTerminal","env": {"OPENAI_BASE_URL": "https://api.deepseek.com","OPENAI_API_KEY": "xxxx"}},{"name": "uv test fastapi dev","type": "debugpy","request": "launch","module": "fastapi","console": "integratedTerminal","args": ["dev", "main.py", "--port", "8090"],"env": {"OPENAI_BASE_URL": "https://api.deepseek.com","OPENAI_API_KEY": "xxxx"}}]
}
筆者這里創建了兩個相關配置uv test single file
是項目單文件的debug配置,另一個uv test fastapi dev
是整個fastapi項目develop模式啟動調試的配置.其中比較關鍵的配置字段是module
,需要指定為fastapi
指令,其本質上還是相當于如下uv指令
OPENAI_BASE_URL='https://api.deepseek.com' OPENAI_API_KEY=xxx uv run -- fastapi dev main.py --port 8090
啟動應用.
vscode中需要給項目指定python解釋器,這一步其實就是將當前虛擬環境中的python解釋器配置為項目的python解釋器,具體在vscode中做法為:
View-->Command Pattle...-->Python:Select Interpreter-->{選擇你的項目文件夾}-->解釋器選擇本項目虛擬環境中的python(./.venv/bin/python)
這樣vscode中debug選擇Python Debugger: Debug using launch.json
選擇uv test fastapi dev
則是以develop模式啟動整個fastapi項目本地調試,選擇uv test single file
則是調試項目中的單個文件.總之這是筆者總結下來的比較舒服的在vscode中開發uv項目的相關配置.
命令行運行
如果不使用vscode直接在命令行啟動運行,那更簡單了,直接參考前面講到的uv運行python包中的快捷指令那一塊使用uv run即可,具體指令如下:
OPENAI_BASE_URL='https://api.deepseek.com' OPENAI_API_KEY=xxx uv run -- fastapi dev main.py --port 8090
深入理解 uv lock, uv sync, uv lock
uv lock 行為解析:
如果項目中沒有uv.lock
文件,那么會根據pyproject.toml
解析出的精確依賴版本
生成uv.lock文件;如果項目中存在uv.lock
文件,根據解析出的精確依賴版本
更新情況決定是否更新當前uv.lock文件.
其中有一個指令uv lock --check
則是解析pyproject.toml檢查是否更行uv.lock.
uv sync 行為解析
uv sync首先需要去檢查uv.lock文件是否是最新的,如果不是,則解析pyproject.toml更新uv.lock中依賴的精確版本,最后再根據uv.lock文件同步下載依賴包到虛擬環境.
uv sync有兩個關鍵參數--locked
和--frozen
,他倆區別如下:
參數 | 說明 |
---|---|
–locked | 檢查uv.lock是否是最新,如果要更新則報錯,如果本身就是最新的則繼續同步過程 |
–frozen | 不進行uv.lock的更新檢測,直接以uv.lock中依賴包版本同步虛擬環境中的依賴 |
使用較多的指令是uv sync --locked
,主要用于CI過程,保證提交的uv.lock文件中的依賴一定是最新版本,如果報錯,證明uv.lock不是最新版本,需要開發提交最新的uv.lock到repo再觸發ci過程.必須保證uv.lock一致性.
uv run 行為解析
可以簡單理解默認的uv run之前要進行uv sync操作.
其實–frozen參數其實是是在實際部署運行時候使用,uv run --frozen xxx
表示不再check uv.lock是否更新,直接運行程序.因為前面CI過程已經保證uv.lock一致且依賴包已經最新.如果是容器部署的uv應用,基本上--frozen
參數會出現在容器入口程序中.
uv項目的docker file
根據上面的所有信息,基本上可以總結出一個uv項目的Dockerfile模板:
FROM python:3.11.13-slimWORKDIR /app
COPY ./ ./RUN pip install uvRUN uv sync --lockedEXPOSE 8090ENTRYPOINT ["uv", "run", "--frozen", "--", "fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8090"]
項目相關文件:
- main.py
#-*- coding:utf-8 -*-from fastapi import FastAPIapp = FastAPI(title='test')@app.get("/")
async def index():return 'hello'
- pyproject.toml
[project]
name = "test"
version = "0.1.0"
description = "a test uv project"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["click>=8.2.1","fastapi[standard]>=0.116.1","httpx>=0.28,<0.29","openai>=1.99.6",
][dependency-groups]
dev = ["pytest>=8.4.1",
]
lint = ["ruff>=0.12.8",
]
構建鏡像docker build -t uv-test:latest . --no-cache
并且運行docker run --rm -p 8090:8090 --name uv-test uv-test:latest
測試:
curl http://localhost:8090
輸出
hello