Installa due progetti Python sulla stessa macchina e il secondo può rompere il primo. Uno richiede requests==2.28, l’altro requests==2.32, e pip ha un’unica cartella site-packages di sistema in cui scrivere. L’ultima installazione vince, in silenzio. Te ne accorgi quando l’altro progetto comincia a lanciare errori che non hanno nulla a che fare con il codice che hai appena modificato.
Un ambiente virtuale dà a ogni progetto il proprio site-packages. Il venv non tocca nulla a livello globale, e due progetti possono dipendere da versioni incompatibili della stessa libreria senza che nessuno dei due se ne accorga.
Cos’è un ambiente virtuale Python?
Un venv è una cartella con tre elementi: un interprete Python (un symlink a quello di sistema su Linux e macOS), un file pyvenv.cfg che punta a quell’installazione di base, e una cartella site-packages dove pip install scrive i pacchetti. Attivarlo mette la cartella bin del venv (Scripts su Windows) in testa al PATH della shell, così python e pip puntano alle copie del venv invece che a quelle di sistema.
Questo è tutto il meccanismo. Non c’è isolamento del filesystem né una seconda copia di Python. Un venv cambia solo dove la shell trova due eseguibili e da dove vengono risolti gli import, motivo per cui crearne uno richiede un secondo e eliminarne uno è rm -rf .venv. Se hai bisogno di isolare anche il sistema operativo e il filesystem, è un lavoro per un container, non per un venv.
Come crei e attivi un venv?
La libreria standard include il modulo venv, quindi non c’è nulla da installare:
| |
.venv è il nome convenzionale, e uv e la maggior parte degli editor lo cercano di default. L’attivazione dipende dalla shell:
| OS / shell | Comando |
|---|---|
| Linux / macOS (bash, zsh) | source .venv/bin/activate |
| Windows (cmd.exe) | .venv\Scripts\activate.bat |
| Windows (PowerShell) | .venv\Scripts\Activate.ps1 |
Un’installazione Windows nuova blocca gli script PowerShell, incluso quello di attivazione. Consenti gli script creati localmente una volta per utente:
| |
Una volta attivo il venv, il prompt riceve il prefisso (.venv) e which python (where python su Windows) punta dentro .venv. Esegui deactivate per tornare indietro. È una funzione della shell definita dallo script di attivazione, non un programma separato.
L’attivazione è opzionale. .venv/bin/python script.py e .venv/bin/pip install requests funzionano anche senza, ed è così che le pipeline CI e gli editor richiamano il venv sotto il cofano.
Con più versioni di Python installate, python -m venv usa il primo python che la shell trova, che spesso non è quello che intendevi. Specifica la versione: python3.12 -m venv .venv su Linux e macOS. Su Windows usa il launcher py incluso in Python, che sceglie una versione per numero indipendentemente dal PATH: py -3.12 -m venv .venv.
Come installi i pacchetti con pip e requirements.txt?
Dentro un venv attivo, pip install <package> fa quello che ti aspetti. Per la riproducibilità, la parte che conta è registrare cosa è stato installato:
| |
pip freeze scrive ogni pacchetto installato con la sua versione esatta, incluse le dipendenze delle tue dipendenze. Fai il commit di requirements.txt, e chiunque clona il progetto esegue:
| |
e ottiene le stesse versioni che avevi tu. Il punto debole: il file mescola quello che hai chiesto tu con tutto quello che pip ha portato con sé. Sei mesi dopo, distinguere “mi serve Flask” da “Flask richiedeva Werkzeug 3.1.3” significa leggere l’intera lista e indovinare.
pyproject.toml o requirements.txt: qual è la differenza?
pyproject.toml registra solo le tue dipendenze dirette, con intervalli di versione:
| |
pip install . legge questo file e risolve l’intero albero delle dipendenze al momento dell’installazione. pip install ".[dev]" aggiunge in più il gruppo dev. Lo stesso file contiene i metadati di packaging se un giorno pubblichi il progetto, qualcosa per cui requirements.txt non è mai stato pensato.
Quello che non fa è fissare versioni esatte. flask>=3.0 si risolve in quella più recente disponibile il giorno in cui installi. Il pinning è il compito di un lock file, e venv + pip non te ne forniscono uno.
In una build di container, questo è il file che copi prima del resto del codice sorgente, così il layer delle dipendenze si ricostruisce solo quando pyproject.toml cambia. La guida alle best practice per Dockerfile spiega perché quell’ordine conta per la cache di build.
Come usi uv al posto di venv e pip?
uv è un singolo binario Rust che copre i compiti di venv, pip, pip-tools e pyenv: uv python install 3.12 scarica e gestisce le versioni di Python come fa pyenv, quindi uv venv --python 3.12 funziona anche se quella versione non è ancora sulla macchina. È anche molto più veloce: una risoluzione che a pip richiede diversi secondi, con uv finisce in una frazione di quel tempo.
Installalo una volta (la documentazione di astral.sh elenca il comando per ogni piattaforma). Puoi poi usarlo in due modi.
Come sostituto diretto di venv e pip, se vuoi solo velocità:
| |
uv venv crea .venv allo stesso modo di python -m venv .venv, e uv pip install accetta gli stessi argomenti di pip. Attivi il risultato come un venv qualsiasi.
In modalità progetto, che aggiunge il locking:
| |
uv init crea lo scaffolding di un pyproject.toml. uv add scrive la dipendenza al suo interno, risolve l’intero albero e scrive un file uv.lock che fissa ogni versione risolta, incluse quelle transitive, con gli hash. Quel lock file è il pezzo che pip non produce. Fai il commit sia di pyproject.toml che di uv.lock, e un collega esegue:
| |
Questo crea .venv se manca e installa esattamente quello che dice il lock file, non quello che gli intervalli di versione in pyproject.toml risolvono quel giorno. uv run script.py esegue lo stesso controllo di sincronizzazione prima di partire, così il tuo ambiente non può andare alla deriva rispetto al lock file senza che tu lo noti.
Come risolvi gli errori più comuni degli ambienti virtuali?
error: externally-managed-environment. Hai eseguito pip install fuori da un venv su Debian 12+, Ubuntu 23.04+, Fedora 38+ o una distro simile, e il Python di sistema ha rifiutato. PEP 668 ha introdotto un file marcatore che il package manager del sistema operativo piazza nella sua installazione di Python per impedire a pip di sovrascrivere pacchetti che gestiscono anche apt o dnf. Prima di allora, un sudo pip install sbagliato poteva rompere gli strumenti stessi del sistema operativo in modi orribili da debuggare. Alcune guide più vecchie ti dicono di aggiungere --break-system-packages. Non farlo: riporta esattamente il danno che il marcatore impedisce. Crea un venv e installa lì.
L’editor esegue l’interprete sbagliato. VS Code, PyCharm e strumenti simili usano quello a cui python puntava quando il progetto è stato aperto, che non è necessariamente il tuo venv. I sintomi: import che funzionano nel terminale appaiono come non risolti nell’editor, oppure un pacchetto installato cinque minuti fa non viene trovato. Imposta esplicitamente l’interprete dell’editor su .venv/bin/python (.venv\Scripts\python.exe su Windows).
Il venv è stato commitato su git. Sono qualche centinaio di megabyte di binari più percorsi assoluti in pyvenv.cfg e negli script di attivazione, quindi si rompe appena qualcuno clona il repo su un percorso o un sistema operativo diverso. Aggiungi .venv/ al .gitignore. Da Python 3.13, venv scrive anche un .gitignore dentro il nuovo ambiente che ne ignora il contenuto, ma sulle versioni più vecchie devi farlo tu. Fai il commit di requirements.txt, oppure di pyproject.toml con uv.lock. Quei file ricostruiscono l’ambiente ovunque. La cartella in sé non appartiene mai al repo.
venv, pyproject.toml o uv: quale scegliere?
Resta con venv + pip + requirements.txt per un progetto piccolo con poche dipendenze, dove non vuoi tooling in più. Passa a pyproject.toml non appena hai dipendenze solo per lo sviluppo come test runner e linter che non devono finire in produzione, o quando pensi di pacchettizzare il progetto.
Aggiungi uv quando la velocità di risoluzione comincia a pesare o vuoi un lock file commitato senza mantenerne uno a mano con pip-tools. Non ti costringe a riscrivere nulla: esegui uv init su un progetto nuovo, oppure uv venv e uv pip install -r requirements.txt su uno esistente, e passa a uv add quando sei pronto per il lock file.