python之uv使用詳解
uv:
conda create -n xxx python=3.11 -yconda activate xxxpip install -r requirements.txt
安裝與更新
這里只講兩種筆者認(rèn)為最多涉及到的安裝與更新方式,分別是命令行standard安裝更新和pip安裝更新
standalone
本地開發(fā)機(jī)器上安裝以及更新的方式,筆者是ubuntu系統(tǒng),所以安裝使用如下指令
curl -LsSf https://astral.sh/uv/install.sh | sh
參考官網(wǎng)uv安裝
更新的指令即是:
UV_NO_MODIFY_PATH=1 uv self update
pip 安裝
pip 安裝適合在構(gòu)建docker鏡像的時候選擇的安裝方式,因?yàn)橐话銟?gòu)建docker鏡像選擇的基礎(chǔ)鏡像都是一個最小化的python basic image.
它可能只包含基本的python環(huán)境和pip,所以在Dockerfile中使用pip安裝uv,安裝指令如下:
pip install uv
既然是pip安裝的包,更新指令就是
pip install --upgrade uv
不過這個更新指令基本使用不到,構(gòu)建鏡像過程中只需要在基礎(chǔ)鏡像安裝uv,不需要更新uv.
創(chuàng)建以及初始化項(xiàng)目
關(guān)鍵指令為
uv init [OPTIONS] [PATH]
比如在python-uv目錄下執(zhí)行uv init --python=3.11 test命令會創(chuàng)建一個名字為test的子目錄且子目錄作為項(xiàng)目根目錄,命令指定了項(xiàng)目運(yùn)行所需的python版本為3.11,同時在test目錄下創(chuàng)建項(xiàng)目配置文件pyproject.toml,配置文件中設(shè)置項(xiàng)目名稱為test,和項(xiàng)目根目錄同名,同時還有一些git repo必需的文件以及文件夾,可見init指令也將項(xiàng)目初始化成一個git repo.先看一下目錄結(jié)構(gòu):
test
├── .git
├── .gitignore
├── main.py
├── pyproject.toml
├── .python-version
└── README.md
如果init指令不指定參數(shù)PATH則是將運(yùn)行uv init所在目錄作為項(xiàng)目根目錄去創(chuàng)建上面這些內(nèi)容.
此時在項(xiàng)目內(nèi)運(yùn)行uv sync才能看到虛擬環(huán)境目錄.venv和uv.lock被創(chuàng)建.
所以完整的項(xiàng)目初始化之后結(jié)構(gòu)如下:
└── test
├── .git
├── .gitignore
├── main.py
├── pyproject.toml
├── .python-version
├── README.md
├── uv.lock
└── .venv
有幾個重要文件和文件夾需要提前說明:
- .venv: 項(xiàng)目python虛擬環(huán)境和依賴包相關(guān)文件夾. 可以在項(xiàng)目目錄上下文執(zhí)行指令
source .venv/bin/activate激活虛擬環(huán)境.但是由于使用uv來構(gòu)建以及管理的項(xiàng)目使用uv指令居多,所以一般情況下不需要顯式的激活這個虛擬環(huán)境.同時.venv也不應(yīng)該上傳到版本控制系統(tǒng),每一次項(xiàng)目repo同步下來只需要運(yùn)行uv sync則可以更新本地的虛擬環(huán)境中l(wèi)ib更新到最新,繼續(xù)開發(fā)項(xiàng)目或者本地調(diào)試. - pyproject.toml: 存放整個項(xiàng)目的元數(shù)據(jù),視作項(xiàng)目配置文件,其內(nèi)部包含項(xiàng)目名稱,描述,依賴,構(gòu)建,腳本以及工具等等,一般執(zhí)行uv相關(guān)命令都會涉及到更改此配置文件,官方關(guān)于此配置文件編寫教程鏈接pyproject.如果從遠(yuǎn)端同步下來uv項(xiàng)目,可以執(zhí)行
uv sync即是根據(jù)此配置文件去構(gòu)建本地的開發(fā)虛擬環(huán)境和依賴包. - uv.lock 文件:項(xiàng)目依賴的精確版本信息,和
pyproject.toml配置不同,配置文件一般是描述依賴的版本邊界約束,比如某個依賴包最低版本或者最高版本.lock文件中是真正虛擬環(huán)境內(nèi)安裝的精確版本,任何涉及到更新依賴的命令比如uv sync,uv add {package}等操作在依賴的版本邊界約束內(nèi)可能拉取最新的版本,就會更新此lock文件.此文件真正起作用的地方是在CI構(gòu)建過程,因?yàn)樘摂M環(huán)境文件不會提交到版本管理系統(tǒng),每次構(gòu)建都需要拉取這些依賴,涉及到使用指令uv sync.由于前面所說這個指令可能會拉取新的依賴版本可能引入不兼容問題,因此使用指令uv sync --frozen強(qiáng)制使用uv.lock文件中的精確版本來完成構(gòu)建過程,保證構(gòu)建前后的版本一致,降低構(gòu)建過程引入新版本帶來的風(fēng)險(xiǎn).uv.lock文件要求必須上傳到版本控制系統(tǒng)且不可以手動更改.
這里可以看到init甚至可以將項(xiàng)目初始化成一個git repo,實(shí)際可能不需要這個功能,多半情況下本地已經(jīng)有g(shù)it repo了只需要創(chuàng)建以及初始化uv 項(xiàng)目即可,這個情況下,可以稍微調(diào)整一下init指令相關(guān)的optional參數(shù)關(guān)閉git repo相關(guān)feature即可.另外--name也可以顯示指定項(xiàng)目的名稱,這樣就不是默認(rèn)的以項(xiàng)目文件夾名字作為項(xiàng)目名稱了,刪除當(dāng)前的test文件夾,使用如下命令創(chuàng)建新的非git repo的uv項(xiàng)目:
uv init test --description="a test uv project" --vcs=none --no-readme --python=3.11 --managed-python
上面指令還會生成一個main.py的入口文件,然后在test目錄下執(zhí)行如下命令可以自動創(chuàng)建虛擬環(huán)境
uv run main.py
輸出:
Using CPython 3.11.13 Creating virtual environment at: .venv Hello from test!
可以看到虛擬環(huán)境文件夾.venv和uv.lock文件都已經(jīng)創(chuàng)建好了,至此項(xiàng)目初始化結(jié)束.
這里簡要說明一下剛創(chuàng)建好的項(xiàng)目是沒有虛擬環(huán)境文件夾.venv的,除了使用uv run main.py之外還可以使用uv sync命令.
本質(zhì)上uv會查看本地是否有項(xiàng)目指定運(yùn)行的python版本,比如本項(xiàng)目初始化的時候指定的是3.11版本的python,沒有的話會先下載此版本python到~/.local/share/uv/python目錄,然后再將此版本python"拷貝"到當(dāng)前項(xiàng)目.venv文件夾中.
可以在項(xiàng)目文件夾為上下文的命令行窗口中執(zhí)行指令source .venv/bin/activate來激活此(使用deactivate則退出)虛擬環(huán)境.只是使用uv指令的時候是完全不需要顯式激活虛擬環(huán)境這個操作.
依賴管理
uv 添加依賴有uv add命令,它會將依賴包安裝到當(dāng)前虛擬環(huán)境.venv中同時更新pyproject.toml中的dependencies配置.
比如需要把最新的fastapi添加到項(xiàng)目中可以使用uv add "fastapi[standard]==0.116"添加一個固定版本的fastapi依賴包.當(dāng)依賴安裝好后查看pyproject.toml可以看到依賴已被添加
dependencies = [
"fastapi[standard]==0.116.0",
]
實(shí)際開發(fā)過程中不會這么嚴(yán)格限制一個版本,需要支持能夠獲取最新的bugfix修訂版本,所以一般依賴都會給定一個版本范圍比如fastapi版本是0.116到0.117版本的最新修訂版本,那么添加依賴指令變?yōu)?code>uv add "fastapi[standard]>=0.116,<0.117",此命令會覆蓋pyproject.toml中的版本約束,同時升級虛擬環(huán)境中安裝的fastapi到0.116.x最新版本修.
當(dāng)然也可以直接修改pyproject.toml里面的fastapi依賴,如下
dependencies = [
"fastapi[standard]>=0.116,<0.117",
]
然后再運(yùn)行uv sync也能達(dá)到同樣效果.但是最佳做法還是使用uv add增加或者修改現(xiàn)有的依賴.
刪除依賴則是uv remove指令.
默認(rèn)情況下如果安裝依賴不指定版本約束,當(dāng)前會安裝最新版本,且dependencies會寫入 dep >= latest version,比如執(zhí)行如下指令
uv add httpx
查看dependencies
dependencies = [
"httpx>=0.28.1",
]
此時安裝的是最新版本,當(dāng)讓可以修改依賴的版本約束uv add "httpx>=0.28,<0.29"限制依賴版本為0.28.x的最新修訂版本.
uv中的依賴主要是三類,第一類是project.dependencies項(xiàng)目依賴,默認(rèn)情況下使用指令uv add xxx的依賴都屬于項(xiàng)目依賴,這些依賴在配置文件里面回添加到[project]配置段下的dependencies里面.這些依賴說白了都是代碼中引入的包,代碼運(yùn)行時必不可少的包.
第二種是dependency groups.就是開發(fā)所需依賴,不會被大包到項(xiàng)目中去,所以這類依賴也不會出現(xiàn)在[project]配置段中,只會出現(xiàn)在[dependency-groups]配置段中.
有兩種使用dependency groups的方法,第一種是uv add --dev xxx把依賴放入dev這個group,可以理解為dev是內(nèi)置的dependency groups.比如將pytest這個包放入devgroup用于項(xiàng)目測試.
uv add pytest --dev
配置文件
[dependency-groups]
dev = [
"pytest>=8.4.1",
]
當(dāng)然要刪除在dev group中的這個包也需要加上–dev flag uv remove pytest --dev. 注意dev這個group在uv sync的時候也會拉取相應(yīng)的依賴包.
除了dev這個官方定義的組之外,還可以自定義組,使用指令uv add --group {group_name} {package}實(shí)現(xiàn),比如:
uv add --group lint ruff
會創(chuàng)建一個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 指令是非常強(qiáng)大的指令. 下面全面說明它的用法:
直接在命令行運(yùn)行python代碼片段
它可以在命令行運(yùn)行一段python代碼片段,比如:
uv run python -c "import sys;print(sys.executable)"
輸出當(dāng)前解釋器的位置
~/.local/share/uv/python/cpython-3.11.13-linux-x86_64-gnu/bin/python3.11
直接運(yùn)行項(xiàng)目中可執(zhí)行腳本文件
它還可以直接運(yùn)行項(xiàng)目中的可執(zhí)行腳本文件,比如項(xiàng)目中存在如下python腳本文件
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import httpx
if __name__ == '__main__':
print(httpx.__version__)
pass
輸出結(jié)果:
0.28.1
這里用到了項(xiàng)目依賴httpx,但是可以在不需要顯式激活當(dāng)前虛擬環(huán)境的情況下直接打印當(dāng)前虛擬環(huán)境中的依賴包信息.
除了python腳本外,uv run還可以直接運(yùn)行shell腳本,比如有如下foo.sh的shell腳本:
python cli.py
可以使用uv run bash foo.sh也能正常運(yùn)行腳本獲取打印的依賴包版本信息.
再看一個細(xì)節(jié),比如下面沒有shebang行的python腳本sample.py
if __name__ == '__main__':
print('hello')
如果直接命令行運(yùn)行./sample.py肯定報(bào)錯,大家都知道需要指定python解釋器運(yùn)行,比如python sample.py使用默認(rèn)的解釋器運(yùn)行就不會報(bào)錯.
使用uv 運(yùn)行此腳本則是命令uv run sample.py也能正確輸出結(jié)果.uv運(yùn)行python腳本的整個過程可以等價于如下操作:
uv sync source .venv/bin/activate python sample.py
當(dāng)我們知道當(dāng)前要運(yùn)行的腳本是python時候,甚至可以直接用指令uv run -- python sample.py運(yùn)行python腳本.
運(yùn)行python包中快捷指令
我們知道有些包安裝后是有在命令行運(yùn)行的快捷指令的,比如環(huán)境中安裝了fastapi[standard],那么在命令行里面就可以使用指令fastapi dev xxx.py以develop模式快速啟動fastapi web app. 那么在uv管理的項(xiàng)目里面依然可以運(yùn)行這種命令行指令.
比如先在uv項(xiàng)目里面添加fastapi[standard]包
uv add "fastapi[standard]"
編寫一個fastapi web app 的 main.py文件:
#-*- coding:utf-8 -*-
from fastapi import FastAPI
app = FastAPI(title='test')
@app.get("/")
async def index():
return 'hello'
uv 命令 develop 模式啟動此app:
uv run -- fastapi dev main.py --port 8090
這里 uv run -- 中的-- 保證此字符后面都是fastapi的命令行指令與其運(yùn)行參數(shù),而不會被解析成uv run參數(shù),非常關(guān)鍵。
curl測試以及輸出結(jié)果:
curl http://localhost:8090 "hello"
因此基本上使用uv在命令行運(yùn)行這些包中的快捷指令基本就是 uv run -- {package cli cmd} {cli params}這樣的形式.
uv項(xiàng)目本地運(yùn)行調(diào)試細(xì)節(jié)
上一部分完全探討了uv run這個指令,這一部分詳細(xì)探討uv項(xiàng)目本地運(yùn)行以及調(diào)試的細(xì)節(jié).
這里涉及到兩種運(yùn)行:
- 1. vscode 中運(yùn)行調(diào)試
- 2. 本地命令行中調(diào)試運(yùn)行細(xì)節(jié)
vscode 中運(yùn)行調(diào)試uv項(xiàng)目
和之前筆者用conda創(chuàng)建虛擬環(huán)境,在vscode中調(diào)試項(xiàng)目那一套基本操作類似.
這里首先需要創(chuàng)建項(xiàng)目vscode debug所需的launch.json文件,文件最基本內(nèi)容如下,可以根據(jù)需求進(jìn)行更改.
{
// 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"
}
}
]
}
筆者這里創(chuàng)建了兩個相關(guān)配置uv test single file是項(xiàng)目單文件的debug配置,另一個uv test fastapi dev是整個fastapi項(xiàng)目develop模式啟動調(diào)試的配置.其中比較關(guān)鍵的配置字段是module,需要指定為fastapi指令,其本質(zhì)上還是相當(dāng)于如下uv指令
OPENAI_BASE_URL='https://api.deepseek.com' OPENAI_API_KEY=xxx uv run -- fastapi dev main.py --port 8090
啟動應(yīng)用.
vscode中需要給項(xiàng)目指定python解釋器,這一步其實(shí)就是將當(dāng)前虛擬環(huán)境中的python解釋器配置為項(xiàng)目的python解釋器,具體在vscode中做法為:
View-->Command Pattle...-->Python:Select Interpreter-->{選擇你的項(xiàng)目文件夾}-->解釋器選擇本項(xiàng)目虛擬環(huán)境中的python(./.venv/bin/python)
這樣vscode中debug選擇Python Debugger: Debug using launch.json 選擇uv test fastapi dev則是以develop模式啟動整個fastapi項(xiàng)目本地調(diào)試,選擇uv test single file則是調(diào)試項(xiàng)目中的單個文件.總之這是筆者總結(jié)下來的比較舒服的在vscode中開發(fā)uv項(xiàng)目的相關(guān)配置.
命令行運(yùn)行
如果不使用vscode直接在命令行啟動運(yùn)行,那更簡單了,直接參考前面講到的uv運(yùn)行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 行為解析
如果項(xiàng)目中沒有uv.lock文件,那么會根據(jù)pyproject.toml解析出的精確依賴版本生成uv.lock文件;如果項(xiàng)目中存在uv.lock文件,根據(jù)解析出的精確依賴版本更新情況決定是否更新當(dāng)前uv.lock文件.
其中有一個指令uv lock --check則是解析pyproject.toml檢查是否更行uv.lock.
uv sync 行為解析
uv sync首先需要去檢查uv.lock文件是否是最新的,如果不是,則解析pyproject.toml更新uv.lock中依賴的精確版本,最后再根據(jù)uv.lock文件同步下載依賴包到虛擬環(huán)境.
uv sync有兩個關(guān)鍵參數(shù)--locked和--frozen,他倆區(qū)別如下:
| 參數(shù) | 說明 |
|---|---|
| –locked | 檢查uv.lock是否是最新,如果要更新則報(bào)錯,如果本身就是最新的則繼續(xù)同步過程 |
| –frozen | 不進(jìn)行uv.lock的更新檢測,直接以uv.lock中依賴包版本同步虛擬環(huán)境中的依賴 |
使用較多的指令是uv sync --locked,主要用于CI過程,保證提交的uv.lock文件中的依賴一定是最新版本,如果報(bào)錯,證明uv.lock不是最新版本,需要開發(fā)提交最新的uv.lock到repo再觸發(fā)ci過程.必須保證uv.lock一致性.
uv run 行為解析
可以簡單理解默認(rèn)的uv run之前要進(jìn)行uv sync操作.
其實(shí)–frozen參數(shù)其實(shí)是是在實(shí)際部署運(yùn)行時候使用,uv run --frozen xxx表示不再check uv.lock是否更新,直接運(yùn)行程序.因?yàn)榍懊鍯I過程已經(jīng)保證uv.lock一致且依賴包已經(jīng)最新.如果是容器部署的uv應(yīng)用,基本上--frozen參數(shù)會出現(xiàn)在容器入口程序中.
uv項(xiàng)目的docker file
根據(jù)上面的所有信息,基本上可以總結(jié)出一個uv項(xiàng)目的Dockerfile模板:
FROM python:3.11.13-slim WORKDIR /app COPY ./ ./ RUN pip install uv RUN uv sync --locked EXPOSE 8090 ENTRYPOINT ["uv", "run", "--frozen", "--", "fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8090"]
項(xiàng)目相關(guān)文件:
- main.py
#-*- coding:utf-8 -*-
from fastapi import FastAPI
app = 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",
]
構(gòu)建鏡像
docker build -t uv-test:latest . --no-cache
并且運(yùn)行
docker run --rm -p 8090:8090 --name uv-test uv-test:latest
- 測試:
curl http://localhost:8090
- 輸出:
hello
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python基礎(chǔ)教程之循環(huán)語句(for、while和嵌套循環(huán))
這篇文章主要給大家介紹了關(guān)于Python基礎(chǔ)教程之循環(huán)語句(for、while和嵌套循環(huán))的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
python的數(shù)據(jù)與matlab互通問題:SciPy
這篇文章主要介紹了python的數(shù)據(jù)與matlab互通問題SciPy,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12
Django對數(shù)據(jù)庫進(jìn)行添加與更新的例子
今天小編就為大家分享一篇Django對數(shù)據(jù)庫進(jìn)行添加與更新的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07

