現(xiàn)代?Python?包管理器?uv的使用詳解
用 uv + Python 開發(fā)命令行工具
當(dāng)使用 uv 寫正規(guī)一點(diǎn)的 CLI 應(yīng)用的時(shí)候,還是應(yīng)該使用
uv init --package [package name]
因?yàn)閷懸粋€(gè)命令行程序總是要安裝的,想分享到 PYPI 也必須要打包。
我都不知道我第一次用 uv 的時(shí)候是怎么正確打包,并能使用 uv 安裝我開發(fā)的程序。當(dāng)時(shí)真是誤打誤撞的,那時(shí)候我根本不知道 pyproject.toml 應(yīng)該怎么寫才能正確打包,更加不知道寫一個(gè)命令行程序的規(guī)范是什么。當(dāng)時(shí)我連 Build Backend 都沒有設(shè)置,純粹是運(yùn)氣好。
目錄結(jié)構(gòu)
一個(gè) Python 應(yīng)用,不管是 CLI 工具,還是后端服務(wù),只要是一個(gè)想分發(fā)給別人用,項(xiàng)目都要有一個(gè)規(guī)范的目錄結(jié)構(gòu), flat-layout or src-layout?,F(xiàn)在比較流行 src-layout,如果沒有什么考量就使用 src-layout。
有固定的目錄結(jié)構(gòu)之后,一個(gè)包(文件夾)里面的__init__.py
很重要,構(gòu)建系統(tǒng)默認(rèn)是只把有 __init__.py
文件的文件夾才當(dāng)作是一個(gè)包。當(dāng)然 __init__.py
可以沒有,沒有 __init__.py
的包叫做 namespace package。要打包 namespace package 需要在 pyproject.toml
里面給構(gòu)建系統(tǒng)指出怎么找到你的包。
Python 項(xiàng)目打包的細(xì)節(jié)我不太清楚,可以看看對(duì)應(yīng)的構(gòu)建后端的文檔,例如 hatch 的文檔。
uv 的使用
uv init
uv init 初始化一個(gè)項(xiàng)目
uv init example-app
默認(rèn)沒有使用 --lib
, uv 就會(huì)使用 --app
,相當(dāng)于
uv init --app example-app
$ tree example-app example-app/ ├── main.py ├── pyproject.toml └── README.md
uv init
創(chuàng)建一個(gè) Python 項(xiàng)目,自動(dòng)建立好 pyproject.toml
,README.md
等文件。在這里你可以隨意創(chuàng)建 .py
文件,也可以手動(dòng)自己把代碼組織成 package。
運(yùn)行對(duì)應(yīng)的 py 文件,也稱為腳本。
uv run main.py
uv 不僅僅是一個(gè)包管理器,還可以說是 Python 構(gòu)建系統(tǒng)的前端,還是 Python 環(huán)境的管理器。
當(dāng)你想寫一個(gè) Python 腳本做點(diǎn)事情,隨便在一個(gè)文件夾運(yùn)行 uv init
幫你創(chuàng)建好虛擬環(huán)境并管理依賴。運(yùn)行腳本就直接使用 uv run script.py
。
uv init --package
如果是寫一個(gè)正經(jīng)的應(yīng)用,想分發(fā)一個(gè)包或者分發(fā)一個(gè)可執(zhí)行的命令,推薦使用 uv init --package
。當(dāng)然也可以先用 uv init
起步,然后再自己手動(dòng)創(chuàng)建包。
uv init --package
這相當(dāng)于 uv init --app --package
。
uv 會(huì)使用 src-layout,把代碼放在 src/
的 Python 包下,uv 會(huì)在 pyproject.toml
中添加一個(gè) [project.scripts]
entrypoint。
我們使用 uv init --package 創(chuàng)建一個(gè) example-pkg,我們?cè)谶@里使用了 --package
。
~> uv init --package example-pkg Initialized project `example-pkg` at `/home/user/example-pkg` ~> tree example-pkg/ example-pkg/ ├── pyproject.toml ├── README.md └── src └── example_pkg └── __init__.py 2 directories, 3 files ~> cd example-pkg/
--package
告訴 uv,我們希望用 Python 包來組織代碼。因?yàn)榇a放在 src/
的文件夾里面,運(yùn)行起來不太方便。uv 在 pyproject.toml
中幫我們聲明了 [project.scripts]
。
[project.scripts] example-pkg = "example_pkg:main"
[project.scripts]
聲明了我們這個(gè)項(xiàng)目會(huì)有哪些命令,當(dāng)執(zhí)行這個(gè) example-pkg 命令的時(shí)候調(diào)用對(duì)應(yīng)的函數(shù)。example_pkg:main
代表 example_pkg
這個(gè)包下的 main 函數(shù)。
See also:
- Writing your pyproject.toml - Python Packaging User Guide
- Creating projects | uv
- PEP 621 – Storing project metadata in pyproject.toml | peps.python.org
創(chuàng)建一個(gè)項(xiàng)目后,為我們生成了一個(gè)命令叫做 example-pkg 要運(yùn)行這個(gè) example-pkg
命令我們有兩個(gè)方式。
(1) 通過 uv run 來運(yùn)行我們的 example-pkg。
~/example-pkg (master)> uv run example-pkg Using CPython 3.11.11 interpreter at: /usr/bin/python3.11 Creating virtual environment at: .venv Built example-pkg @ file:///home/user/example-pkg Installed 1 package in 0.75ms Hello from example-pkg!
(2) 可以激活 uv 給我們創(chuàng)建好的虛擬環(huán)境,再運(yùn)行我們的 example-pkg,我們就不需要通過 uv run 來運(yùn)行這個(gè)命令了。uv run 的解釋見下文。
~/example-pkg (master)> source .venv/bin/activate.fish (example-pkg) ~/example-pkg (master)> example-pkg Hello from example-pkg!
.venv
會(huì)在首次使用 uv run
自動(dòng)創(chuàng)建。也可以使用 uv run
創(chuàng)建 venv
虛擬環(huán)境。uv sync
更新虛擬環(huán)境。
通常克隆下別人的項(xiàng)目后在項(xiàng)目的根目錄運(yùn)行 uv sync
,uv
就會(huì)下載好需要的依賴并創(chuàng)建虛擬環(huán)境。
uv init --package
創(chuàng)建項(xiàng)目的時(shí)候 uv 給我們創(chuàng)建了一個(gè)和項(xiàng)目名字一樣的命令。
我們來加一個(gè) hello-pkg
命令。
--- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [] [project.scripts] example-pkg = "example_pkg:main" +hello-pkg = "example_pkg:main" [build-system] requires = ["hatchling"]
我們運(yùn)行 hello-pkg
,uv 發(fā)現(xiàn) pyproject.toml
更新后自動(dòng)重新安裝了我們的包。這個(gè)包會(huì)被安裝在 .venv
。
~/example-pkg (master)> uv run hello-pkg Built example-pkg @ file:///home/user/example-pkg Uninstalled 1 package in 0.81ms Installed 1 package in 0.67ms Hello from example-pkg!
如果你習(xí)慣在虛擬環(huán)境里面開發(fā),不想每次都運(yùn)行命令都要使用 uv run
,更新 pyproject.toml
后要運(yùn)行一下 uv sync
更新當(dāng)前的虛擬環(huán)境。
這是你開發(fā)的命令程序,你可以把你的程序安裝用戶的全局。
uv tool install -e .
這樣就不必激活虛擬環(huán)境,也不必使用 uv run hello-pkg
。
~> hello-pkg Hello from example-pkg!
-e
代表 editable 安裝,意味著你改動(dòng)代碼后是不需要重新安裝的。如果你更新了 pyproject.toml
或者其他的改動(dòng)需要重新安裝,你可以運(yùn)行 uv tool upgrade example-pkg
。
editable 安裝是 pip 也支持的命令,Python 3.6 就能用。
uv run
我的代碼可能放在 Python 包里,也可能是一個(gè)單獨(dú)的 .py 腳本,或者是一個(gè)腳本沒有任何文件擴(kuò)展名。
uv run
確保你運(yùn)行的命令/腳本是在一個(gè) Python 的環(huán)境中。
當(dāng)你使用 uv init hello-uv-run
創(chuàng)建了一個(gè)項(xiàng)目。
~ $ uv init hello-uv-run Initialized project `hello-uv-run` at `/home/user/hello-uv-run` ~ $ cd hello-uv-run/ ~/hello-uv-run (master) $ eza -l .rw-r--r-- 90 user 9 Jun 23:04 main.py .rw-r--r-- 158 user 9 Jun 23:04 pyproject.toml .rw-r--r-- 0 user 9 Jun 23:04 README.md
第一次使用 uv run
,uv 創(chuàng)建了虛擬環(huán)境,uv 會(huì)確保你的 main.py 腳本是在創(chuàng)建的虛擬環(huán)境里面運(yùn)行的。這個(gè)時(shí)候我們的代碼就放在一個(gè)單獨(dú)的 .py 腳本文件里面。
$ uv run main.py Using CPython 3.12.8 interpreter at: /usr/bin/python3.12 Creating virtual environment at: .venv Hello from hello-uv-run!
我們給這個(gè)項(xiàng)目添加一個(gè)名為 typer 的依賴。演示一下如何使用庫(kù)提供的 CLI 工具。
~/hello-uv-run (master)> uv add typer Resolved 10 packages in 13ms Installed 8 packages in 17ms + click==8.2.1 + markdown-it-py==3.0.0 + mdurl==0.1.2 + pygments==2.19.1 + rich==14.0.0 + shellingham==1.5.4 + typer==0.16.0 + typing-extensions==4.14.0
typer 提供了一個(gè)命令行程序 typer。 我們可以使用 typer 來運(yùn)行我們的 main.py 這個(gè)腳本。此時(shí)我們沒有激活 venv 虛擬環(huán)境,運(yùn)行 uv add
的 typer
命令,要使用 uv run typer
。
~/hello-uv-run (master)> cat main.py def main(name: str): print(f"Hello {name} from hello-uv-run!") ~/hello-uv-run (master)> uv run typer main.py run Joe Hello Joe from hello-uv-run!
激活 venv 虛擬環(huán)境,就可以直接運(yùn)行 typer 命令。
(hello-uv-run) ~/hello-uv-run (master)> typer main.py run joe Hello joe from hello-uv-run!
uv run 運(yùn)行一個(gè)命令或者腳本就和你激活虛擬環(huán)境后運(yùn)行命令或者腳本一樣。
有時(shí)候你需要給命令指定參數(shù),為了不讓 uv 誤以為參數(shù)是 uv run
的,你可以這樣:
uv run -- python -m src.example
這樣就 -m
就會(huì)正常傳遞給 python
。如此常見的命令當(dāng)然 uv 有直接的支持,uv run -m
運(yùn)行一個(gè) Python 模塊。
下面解釋一下前面的 typer 命令。typer 命令行程序的用處是:即便你只有一個(gè) .py 腳本也能實(shí)現(xiàn)命令行的自動(dòng)補(bǔ)全,腳本文件名通常不是一個(gè)命令的名字。typer --install-completion
安裝自動(dòng)補(bǔ)全后,使用 typer main.py run
就能獲得命令行的自動(dòng)補(bǔ)全。
下面這個(gè)例子來自 Typer 的官網(wǎng)。我們將代碼保存到 main.py。
import typer app = typer.Typer() @app.command() def hello(name: str): print(f"Hello {name}") @app.command() def goodbye(name: str, formal: bool = False): if formal: print(f"Goodbye Ms. {name}. Have a good day.") else: print(f"Bye {name}!") if __name__ == "__main__": app()
因?yàn)槲覀冃枰苯舆\(yùn)行 typer 命令才能獲得命令行的自動(dòng)補(bǔ)全,我們有兩個(gè)辦法:(1) 使用 uv tool install typer 安裝 typer 到全局。(2) 激活 venv 虛擬環(huán)境,因?yàn)槲覀冎耙呀?jīng)使用 uv add typer 將 typer 安裝到了虛擬環(huán)境。
我們?cè)?Shell 里面敲 typer main.py run
然后按 Tab 鍵就能得到命令行參數(shù)的補(bǔ)全了。
~/hello-uv-run (master)> typer main.py run goodbye hello
main.py
只是一個(gè)腳本,并不是一個(gè)包。一個(gè)腳本算不上是一個(gè)完整的應(yīng)用(或者項(xiàng)目),Python 被稱之為腳本語(yǔ)言,但 Python 能做的不僅僅是腳本,Python 能開發(fā)一個(gè)完整的應(yīng)用,JavaScript/Lua 也是。
單純的腳本,或許只有 Bash 這樣的才算吧。畢竟連 JavaScript 都有模塊,有構(gòu)建,有包(npm 包)。
我們可以通過 Shebang,chmod 賦予腳本可執(zhí)行權(quán)限,把腳本名字的 .py
后綴去掉,手動(dòng)做到一個(gè)腳本看起來是一個(gè)獨(dú)立且完整的命令行程序。但是,這不是我們開發(fā) Python 命令行程序的方式,我們常規(guī)的方式是創(chuàng)建一個(gè) Python 的包,使用 entry_point 機(jī)制提供命令行程序/GUI程序。uv init --package
就是一個(gè)示例。
我一直在強(qiáng)調(diào),你應(yīng)該用包把 Python 代碼組織起來。可我總不能寫任何一個(gè) Python 代碼都創(chuàng)建一個(gè)包吧。
比方說你可能想在你的 Python 項(xiàng)目里面寫一個(gè)小腳本,測(cè)試你的想法,試用一下某個(gè)庫(kù),或者試試開發(fā)的包的某一個(gè)函數(shù)實(shí)現(xiàn)對(duì)不對(duì),或者在代碼倉(cāng)庫(kù)中給出使用你的 Python 包的 Python 腳本示例。
運(yùn)行你項(xiàng)目里的 .py
腳本你只需要使用 uv run script.py
就可以。
uv run
后面可以指定的是命令/腳本。如果你的腳本是 hello-uv-run/main.py
你只需要 uv run main.py
。如果你的腳本的后綴不是 .py,是沒有后綴的。你可以加一個(gè) --script
選項(xiàng)。
uv run --script main
uv run
能自動(dòng)更新虛擬環(huán)境,uv run
能知道你項(xiàng)目的可執(zhí)行腳本([project.scripts]
),uv run
也能幫你運(yùn)行你不想放在包里面的腳本,哪怕你不想命名腳本為 .py
結(jié)尾的文件。uv 幫你處理好環(huán)境問題,你不需要提前激活虛擬環(huán)境,也不需要提前把你開發(fā)的包安裝到虛擬環(huán)境,只要你使用 uv run
來啟動(dòng)你寫的腳本/命令就能找到它應(yīng)該找到的包。
不要忘了前面提到的方法,你還可以這樣來指定運(yùn)行 Python 腳本時(shí)給 Python 的選項(xiàng)。例如:
uv run -- python -i main.py
還是那句話,你想要代碼被分發(fā),你就要把代碼放在一個(gè)包里面,有 __init__.py
的文件夾才是 Python 的包。如果一個(gè)文件夾里面沒有 __init__.py
,打包的時(shí)候,這個(gè)文件夾下的代碼就不會(huì)被打包,文件夾名字對(duì)應(yīng)的 Python 包也不存在。
如果你的包要提供一個(gè)可執(zhí)行的腳本,就在 [project.scripts]
里面聲明一個(gè)命令,這個(gè)命令對(duì)應(yīng)的是包下的某一個(gè)模塊的某一個(gè)函數(shù)。這似乎是唯一的方式。之前的那種在包之外創(chuàng)建一個(gè)腳本調(diào)用包是過時(shí)的做法。不管你是使用 setup.py 還是 pyproject.toml,現(xiàn)在推薦的做法都是聲明一個(gè) entry_points。
我問了 DeepSeek,為什么現(xiàn)在的 pyproject.toml 不支持單獨(dú)寫一個(gè)腳本作為命令執(zhí)行的入口。下面是它的回答。
現(xiàn)代 Python 項(xiàng)目更傾向于使用
entry_points
和包內(nèi) CLI 代碼,因其在兼容性、維護(hù)性和工具鏈支持上的顯著優(yōu)勢(shì)。通過
setup.py
或pyproject.toml
的entry_points
機(jī)制,可以直接將包內(nèi)的函數(shù)注冊(cè)為命令行工具,無(wú)需單獨(dú)維護(hù)腳本文件。一些老的 setup.py 里面會(huì)看到 setup 傳一個(gè) scripts 參數(shù),指定一個(gè)單獨(dú)的 python 腳本作為命令行程序的入口,這在現(xiàn)代 python 是不推薦的做法。
TIPS:
開發(fā)的時(shí)候希望從環(huán)境變量讀取 API KEY 或者數(shù)據(jù)庫(kù)的連接地址和密碼。創(chuàng)建一個(gè) .env
文件,uv run 支持從指定文件讀取環(huán)境變量。
uv run --env-file .env main.py
如果你使用的是 Fish shell,你可以通過環(huán)境變量設(shè)置 uv 默認(rèn)的 ENV_FILE。
set -Ux UV_ENV_FILE .env
uv tool
如果你只想臨時(shí)運(yùn)行一個(gè)由 Python 包提供的命令,你可以使用
uv tool run
uv tool run
是臨時(shí)安裝命令到一個(gè)隔離的環(huán)境中。uv tool install
可以取代 pipx
。
如果你不想激活虛擬環(huán)境也不想切換你的工作目錄,你就想運(yùn)行一下本地計(jì)算機(jī)上你開發(fā)的某個(gè) Python 包提供命令。
uv tool run --from <package path> package-command
是的,這非常有用,尤其是你想運(yùn)行一個(gè)命令,但是又不想安裝它。不管這個(gè)命令從網(wǎng)上能下載到的,還是在你本地計(jì)算機(jī)上暫時(shí)不安裝到全局的。
如果你要使用 pip 這個(gè)包管理器安裝 cmake/meson,你不應(yīng)該使用系統(tǒng)自帶的 pip,因?yàn)檫@會(huì)污染系統(tǒng)的 Python 環(huán)境,安裝一個(gè)獨(dú)立的應(yīng)用,應(yīng)該使用 pipx 而不是 pip。不過現(xiàn)在有 uv 這樣的工具,估計(jì)也不太會(huì)再使用 pipx 了。
你開發(fā)的命令行工具可以直接使用 uv tool install 安裝,只需指定 git 的 clone 地址。
uv tool install
uv workspace
創(chuàng)建一個(gè) pyproject.toml
表示這個(gè)目錄是一個(gè) Python 項(xiàng)目。
$ uv init --bare example Initialized project `example`
在 example 目錄下創(chuàng)建新的 uv 項(xiàng)目都會(huì)自動(dòng)更新 example/pyproject.toml
, 一個(gè)項(xiàng)目依賴 workspace 的其他項(xiàng)目可以直接使用 uv add projectname 添加,就像這個(gè)項(xiàng)目已經(jīng)放在 PYPI 上一樣。
總結(jié)
uv 的 CLI 規(guī)范和 Go 有點(diǎn)類似,尤其是 go help 和 uv help。uv 的任何命令有不理解的地方可以查看一下這個(gè)命令的 help。
如果希望有一個(gè)命令行命令,所有 CLI 邏輯應(yīng)放在包的模塊內(nèi)(如 src/example_pkg/cli.py
),通過 __init__.py
定義包,而非分散的腳本文件?;蛘咚械?CLI 邏輯單獨(dú)放在一個(gè)包里面(如 src/cli/__init__.py
)。
uv run
? 自動(dòng)處理虛擬環(huán)境,直接運(yùn)行腳本或命令。你可以把代碼放在包里,也可以不放在包里。
uv run script.py
運(yùn)行項(xiàng)目里面的腳本,script.py
在src/
之外。uv run --script myscript
運(yùn)行項(xiàng)目里面的腳本沒有.py
后綴uv run -- python -m mymodule
運(yùn)行虛擬環(huán)境的 python 使用 -m 選項(xiàng),將模塊作為腳本運(yùn)行uv run console-script
運(yùn)行在[project.scripts]
中聲明的console-script
如果你激活了虛擬環(huán)境,你就可以把 uv run
去掉了。如果你使用 uv tool 安裝 Python 包,就可以不激活運(yùn)行 console-script
。
舊版 setup.py
中通過 scripts=["scripts/myscript.py"]
的方式已被淘汰。
你想要代碼被分發(fā),你就要把代碼放在一個(gè)包里面,有 __init__.py
的文件夾才是 Python 的包。
本文就先寫到這里了,有了這些知識(shí)后相信你已經(jīng)知道如何在用 Python 開發(fā)應(yīng)用/庫(kù)的過程使用 uv 了。尤其是強(qiáng)大的 uv run 讓你的代碼隨意組織,又能方便運(yùn)行。在使用 uv 的項(xiàng)目中,你可以又腳本,也可以有包,可以將一個(gè)腳本慢慢變成包。uv 一定會(huì)成為你 Python 開發(fā)過程中一件趁手的工具。
到此這篇關(guān)于現(xiàn)代 Python 包管理器 uv的文章就介紹到這了,更多相關(guān)現(xiàn)代 Python 包管理器 uv內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python pcm音頻添加頭轉(zhuǎn)成Wav格式文件的方法
今天小編就為大家分享一篇python pcm音頻添加頭轉(zhuǎn)成Wav格式文件的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-01-01使用python爬取taptap網(wǎng)站游戲截圖的步驟
這篇文章主要介紹了使用python爬取taptap游戲截圖的步驟,幫助大家更好的理解和學(xué)習(xí)使用python進(jìn)行爬蟲,感興趣的朋友可以了解下2021-05-05python的scipy.stats模塊中正態(tài)分布常用函數(shù)總結(jié)
在本篇內(nèi)容里小編給大家整理的是一篇關(guān)于python的scipy.stats模塊中正態(tài)分布常用函數(shù)總結(jié)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2021-02-02Sklearn調(diào)優(yōu)之網(wǎng)格搜索與隨機(jī)搜索原理詳細(xì)分析
這篇文章主要介紹了Sklearn調(diào)優(yōu)之網(wǎng)格搜索與隨機(jī)搜索原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02Python使用Opencv實(shí)現(xiàn)圖像特征檢測(cè)與匹配的方法
這篇文章主要介紹了Python使用Opencv實(shí)現(xiàn)圖像特征檢測(cè)與匹配的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10解決Django模板無(wú)法使用perms變量問題的方法
這篇文章主要給大家介紹了關(guān)于解決Django模板無(wú)法使用perms變量問題的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09python中pip無(wú)法正確安裝或路徑出錯(cuò)的解決方案
這篇文章主要介紹了python中pip無(wú)法正確安裝或路徑出錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02