Se hai iniziato un progetto Python nel 2020, la tua cintura degli attrezzi era così: pyenv per installare le versioni di Python, venv per creare i virtual environment, pip per installare i pacchetti, pip-tools (o Poetry) per fare il lock, e setuptools per costruire i wheel. Cinque strumenti, cinque file di configurazione, cinque punti dove le cose potevano andare storte.
Nel 2026, sempre più persone usano semplicemente uv.
Cos’è uv
uv è un package e project manager per Python di Astral, la stessa azienda dietro Ruff. È stato lanciato all’inizio del 2024 come sostituto più veloce di pip, poi nel corso dell’anno successivo è cresciuto silenziosamente fino a poter sostituire l’intero stack:
- creazione di virtual environment (sostituisce
venv,virtualenv) - installazione dei pacchetti (sostituisce
pip) - risoluzione e locking delle dipendenze (sostituisce
pip-tools, parti di Poetry) - inizializzazione e gestione dei progetti (sostituisce parti di Poetry, PDM, Hatch)
- gestione dell’interprete Python (sostituisce
pyenv) - integrazione con il build backend (funziona con hatchling, setuptools, ecc.)
È scritto in Rust, distribuito come singolo binario statico e, questa non è fuffa di marketing, davvero da 10 a 100 volte più veloce di pip per la maggior parte delle operazioni. Le demo “installa cento pacchetti in due secondi” sono reali. A metà 2025 era diventato la raccomandazione di default nella maggior parte dei tutorial Python, e a partire dal 2026 è quello che userei per un progetto nuovo.
Installarlo
Non si fa pip install uv (be’, puoi, ma non dovresti: uv è uno strumento, non un pacchetto Python). Installalo come binario di sistema:
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# o tramite il tuo package manager
brew install uv
Tutto qui. Non serve Python per installare uv stesso, il che è bello perché uv può poi andare a installare Python per te.
Il workflow “voglio solo un progetto”
Questa è la strada che farai il 90% delle volte:
uv init weather-cli
cd weather-cli
Questo ti dà una directory popolata:
weather-cli/
├── .python-version
├── .gitignore
├── README.md
├── main.py
└── pyproject.toml
Il pyproject.toml è già compilato con default sensati. Aggiungi una dipendenza:
uv add httpx
Tre cose succedono all’istante:
httpx(e le sue dipendenze) vengono aggiunte a[project.dependencies]inpyproject.toml.- Una
.venv/viene creata se non esiste, e i pacchetti vengono installati al suo interno. - Un file
uv.lockviene generato, pinnando ogni pacchetto e dipendenza transitiva a una versione esatta con hash.
Esegui il tuo codice:
uv run python main.py
uv run si assicura che il venv sia in sync con il lockfile prima di eseguire, così non puoi mai finire nello stato “ho dimenticato di installare qualcosa?”. Se pyproject.toml dice che ti serve un pacchetto che non è installato, uv lo installa. Se il lockfile è obsoleto, uv lo aggiorna. Non attivi niente. Non pensi a niente.
Per rimuovere un pacchetto: uv remove httpx. Per aggiornare tutto: uv lock --upgrade poi uv sync. Per installare una versione specifica: uv add 'httpx>=0.27,<0.30'.
Il lockfile
uv.lock è l’equivalente di poetry.lock o del requirements.txt compilato di pip-tools. Registra la versione esatta risolta di ogni pacchetto, ogni dipendenza transitiva, ogni URL e ogni hash. Committalo su git. È quello che rende le build riproducibili: chiunque cloni il tuo repo ed esegua uv sync otterrà pacchetti byte per byte identici a quelli che hai tu.
pyproject.toml dice cosa vuoi (httpx>=0.27). uv.lock dice cosa hai ottenuto (httpx==0.27.2, più i 14 pacchetti transitivi che si è portato dietro). Quando fai il deploy, installi dal lock, non dalle dichiarazioni lasche. È l’unico modo per evitare il “ma in dev funzionava”.
La modalità compatibile con pip
Se hai un progetto esistente e non vuoi cambiare il tuo workflow, uv ha un sostituto drop-in di pip:
uv pip install requests
uv pip install -r requirements.txt
uv pip freeze > requirements.txt
uv venv # crea un venv (sostituisce python -m venv)
I flag e il comportamento rispecchiano quelli di pip, ma sono molto più veloci. Era questa la modalità originale di uv al lancio, ed è ancora utile per progetti legacy, script CI e qualunque cosa parli già “pip”. Ma per i progetti nuovi, preferisci la project mode (uv add, uv run): è dove uv brilla davvero.
Gestione dell’interprete Python
Questa è la parte che mi ha sorpreso la prima volta che l’ho usata. uv può installare Python stesso.
uv python install 3.13
uv python list
Sono build di Python reali (dal progetto python-build-standalone), installate nella cache di uv. Quando crei un progetto, uv legge requires-python dal tuo pyproject.toml e o usa un interprete installato che corrisponde, o ne scarica uno automaticamente.
# pyproject.toml
[project]
requires-python = ">=3.13"
Se la 3.13 non è installata, uv sync la scarica. Lo step pyenv del vecchio workflow semplicemente sparisce. (Puoi comunque usare il Python di sistema o quelli installati con pyenv se vuoi: uv li guarda tutti.)
C’è anche un file .python-version (creato da uv init) che pinna il progetto a una versione specifica, così ogni collaboratore ottiene lo stesso interprete senza coordinazione.
Migrare da altri strumenti
Da pip + venv + requirements.txt:
cd existing-project
uv init --no-readme --no-pin-python # crea pyproject.toml, non sovrascrive
uv add -r requirements.txt # importa i tuoi pin esistenti
Adesso hai un pyproject.toml e un uv.lock. Cancella requirements.txt quando ti senti sicuro.
Da Poetry: uv legge parzialmente i pyproject.toml in stile Poetry, ma la strada più pulita è uvx migrate-to-uv (uno strumento di conversione one-shot). Riscrive le sezioni [tool.poetry] nella forma [project] della PEP 621 e genera un uv.lock a partire dal tuo poetry.lock.
Da conda: uv non sostituisce conda per lo stack scientifico con dipendenze C/Fortran: quello è ancora il punto forte di conda. Ma per progetti puramente Python, di solito puoi sostituire conda con uv e ottenere ambienti più veloci e leggeri. Per progetti misti, pixi (anche lui Rust, anche lui veloce) è l’equivalente nella forma di conda.
Walkthrough di un progetto nuovo
Iniziamone uno da zero, dall’inizio alla fine. Immagina un piccolo script ETL che pesca JSON da un’API e scrive Parquet.
uv init etl-job --python 3.13
cd etl-job
Aggiungi le dipendenze:
uv add httpx polars
uv add --dev pytest ruff mypy
--dev le aggiunge a un dependency group dev (PEP 735) invece che alle dependencies principali. Vengono installate nel tuo ambiente locale ma non vengono spedite con il pacchetto.
Guarda pyproject.toml:
[project]
name = "etl-job"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"httpx>=0.27.2",
"polars>=1.5.0",
]
[dependency-groups]
dev = [
"pytest>=8.3",
"ruff>=0.5",
"mypy>=1.11",
]
Scrivi un test:
# tests/test_etl.py
def test_arithmetic():
assert 2 + 2 == 4
Eseguilo:
uv run pytest
uv si assicura che pytest e le sue dipendenze siano installati (lo sono: stanno nel gruppo dev, che è installato per default), poi li esegue nel venv del progetto. Stesso pattern per tutto:
uv run ruff check .
uv run mypy src/
uv run python -m etl_job
Ti serve uno strumento una tantum che non fa parte del tuo progetto? Usa uvx (alias di uv tool run):
uvx black . # esegui black senza installarlo in modo permanente
uvx --from cookiecutter cookiecutter gh:audreyr/cookiecutter-pypackage
uvx esegue strumenti effimeri e isolati, come pipx run ma istantaneo perché uv tiene tutto in cache. Lo uso decine di volte al giorno per cose come uvx ruff format, uvx httpie, uvx ipython.
Perché è così veloce
Alcune ragioni contano:
- Download paralleli. pip scarica i pacchetti in serie; uv scarica in parallelo e inizia a estrarre i wheel non appena arrivano i byte.
- Resolver più intelligente. uv usa PubGrub, lo stesso algoritmo che usano Cargo e pub di Dart. Fa backtracking in modo intelligente e dà messaggi d’errore molto migliori quando una risoluzione fallisce.
- Cache globale con hard link. uv tiene una sola copia di ogni versione di pacchetto su disco e fa hard link nei venv dei progetti. Creare un venv fresco con 50 pacchetti richiede centinaia di millisecondi perché in realtà non viene copiato nulla.
- Niente startup di Python a ogni chiamata. pip è un programma Python, quindi ogni invocazione paga la tassa di startup di Python. uv è un binario nativo; parte in pochi millisecondi.
Lo senti soprattutto sulle install a freddo e in CI. Un tipico pip install -r requirements.txt per un progetto con 50 dipendenze potrebbe richiedere 30-60 secondi. La stessa cosa con uv sync spesso sta sotto i due secondi.
CI con uv
La storia in CI è dove la differenza di velocità si compone. Un job tipico di GitHub Actions:
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- run: uv sync --frozen
- run: uv run pytest
- run: uv run ruff check .
--frozen dice a uv di rifiutarsi di aggiornare il lockfile: se pyproject.toml e uv.lock non sono in sync, la build fallisce invece di rifare la risoluzione in silenzio. È quello che vuoi in CI: le build o corrispondono esattamente al lockfile committato o non vengono eseguite. enable-cache: true mantiene la cache dei pacchetti di uv tra una run e l’altra, così la tua seconda push installa in millisecondi.
È anche qui che il trucco degli hard link di uv ripaga: anche con cache fredda, scaricare e risolvere 50 pacchetti su un runner CI di solito finisce prima che la prima riga di log dell’action scorra via.
Cosa non fa
- Non è un build backend. Usa hatchling (o il tuo preferito). uv chiama il backend tramite la PEP 517.
- Non è uno strumento di pubblicazione… be’, quasi.
uv publishesiste nel 2026 e carica su PyPI, ma il workflow è ancora “costruisci il wheel, poi caricalo”. - Non gestisce dipendenze non Python. Se ti serve libpq o ffmpeg installati a livello di sistema, uv non aiuta. (Per quello, guarda pixi o il package manager del tuo OS.)
- Non sostituisce la magia di rilevamento Python del tuo IDE. VS Code, PyCharm, ecc. devono essere puntati a
.venv/(o al suo path assoluto) come con qualsiasi altro strumento. La maggior parte degli editor rileva automaticamente una directory.venvnella radice del progetto, che è esattamente quello cheuvcrea.
Cose da sapere che non stanno nei docs
Alcuni dettagli che mi hanno morso prima di impararli:
uv synccancella i pacchetti che non sono nel lockfile. È il comportamento giusto: tiene il venv consistente con quello che è tracciato. Ma se faiuv pip install qualcosa-sperimentaleper provarlo, il prossimouv syncte lo strappa via. Per tenerlo, eseguiuv addinvece.uv runfa sempre re-sync prima a meno che tu non passi--no-sync. Se hai un check di pre-flight lungo o una risoluzione del lockfile lenta, questo aggiunge latenza. Per un loop interno stretto (per esempio, eseguire ripetutamente un singolo test),--no-syncè tuo amico.- Le versioni degli strumenti sono indipendenti.
uvx ruffesegue l’ultima ruff.uv run ruffesegue la versione pinnata nelle dipendenze di sviluppo del tuo progetto. Sono comandi diversi e possono non essere d’accordo. Per il lint relativo al progetto, preferisciuv runcosì tutti ottengono la stessa versione.
Il punto
Cinque strumenti collassati in uno. Il workflow è uv init, uv add, uv run, uv sync. L’overhead mentale è sparito. Se stai iniziando un nuovo progetto Python nel 2026, questa è la strada di minor resistenza, e quella su cui la maggior parte dell’ecosistema ha convergiuto.
Adesso che sai installare le cose e fare il lock, la prossima lezione è su dove metterle: come dovrebbe essere strutturato un progetto Python, src/ versus flat, dove vivono i test e gli script, e le convenzioni che si sono assestate entro il 2026.