Salta ai contenuti

Tools and Workflows for Python Development

Overview Image

Gestione degli Ambienti di Sviluppo Python, il Controllo di Versione e le Metodologie Operative Moderne

Sezione intitolata “Gestione degli Ambienti di Sviluppo Python, il Controllo di Versione e le Metodologie Operative Moderne”

Il presente documento si prefigge lo scopo di fornire un’analisi esaustiva, rigorosa e dettagliata concernente le metodologie standardizzate e le strumentazioni di nuova generazione per la configurazione, lo sviluppo e la distribuzione di progetti software basati sul linguaggio Python. Saranno oggetto di disamina approfondita i meccanismi intrinseci agli ambienti virtuali (venv e conda), le strategie di risoluzione delle dipendenze (pip, pipx), l’integrazione stretta con i sistemi di controllo versione (Git/GitHub) e l’adozione di gestori di pacchetti avanzati (uv). L’obiettivo ultimo è l’ottimizzazione delle prestazioni, la garanzia della riproducibilità deterministica degli ambienti e la mitigazione dei rischi derivanti da una gestione approssimativa dell’ecosistema software.

  1. Fondamenti Teorici: Isolamento delle dipendenze e architettura del software

    Nell’ambito dell’ingegneria del software orientata al linguaggio Python, la gestione delle dipendenze non costituisce un mero dettaglio accessorio, bensì si configura come pilastro architetturale imprescindibile per la stabilità del sistema. A differenza dei linguaggi compilati (quali C++ o Go), i quali operano mediante il collegamento statico delle librerie producendo binari autosufficienti, l’interprete Python risolve le importazioni in fase di esecuzione (runtime). Esso procede alla ricerca dei moduli interrogando sequenzialmente i percorsi definiti nella variabile sys.path (derivata dalla variabile d’ambiente PYTHONPATH e dalle configurazioni di installazione). Tale dinamica comporta che la presenza di versioni multiple di una medesima libreria, o l’aggiornamento indiscriminato di un pacchetto condiviso, possa innescare conflitti di dipendenze (c.d. dependency hell), con conseguenti malfunzionamenti, errori di importazione e comportamenti imprevedibili dell’applicazione.

1.1 Analisi delle criticitĂ  inerenti alle installazioni a livello di sistema

Sezione intitolata “1.1 Analisi delle criticità inerenti alle installazioni a livello di sistema”

Qualora si proceda all’installazione di un pacchetto a livello di sistema (comunemente definita “globale”), esso viene collocato nella directory site-packages condivisa dall’interprete principale del sistema operativo. Tale modus operandi risulta insostenibile e potenzialmente distruttivo per due ordini di ragioni critiche:

  1. Conflitto di versioni (c.d. Dependency Hell): Nell’ipotesi in cui il Progetto A richieda la libreria pandas v1.0 per funzionalità deprecate e, contestualmente, il Progetto B necessiti di pandas v2.0 per nuove implementazioni, risulta impossibile soddisfare simultaneamente ambedue i requisiti all’interno di un unico ambiente globale. L’aggiornamento di una libreria a beneficio di un progetto comprometterebbe inevitabilmente l’integrità funzionale dell’altro, generando errori a runtime spesso di difficile diagnosi.

  2. Compromissione dell’integrità e permessi di sistema: Sui sistemi operativi basati su Unix (Linux/macOS), l’installazione globale richiede sovente privilegi amministrativi (sudo). Tale pratica comporta il rischio concreto di sovrascrittura o alterazione di librerie critiche, essenziali per il corretto funzionamento del sistema operativo medesimo (molti strumenti di sistema, come dnf o apt, dipendono da specifiche versioni di librerie Python). Al fine di prevenire tali scenari, le recenti distribuzioni Linux hanno adottato la PEP 668, la quale impedisce attivamente a pip di modificare l’ambiente gestito esternamente, imponendo di fatto l’uso di ambienti virtuali.

Un ambiente virtuale (virtual environment) si definisce come una struttura di directory autocontenuta e isolata, la quale opera quale installazione Python indipendente. Da un punto di vista tecnico, essa comprende:

  • Una replica (o, piĂš frequentemente nei sistemi POSIX, un collegamento simbolico) dell’eseguibile binario dell’interprete Python.

  • Il file di configurazione pyvenv.cfg, il quale istruisce l’interprete sulla locazione della “home” dell’ambiente, modificando dinamicamente il prefisso di sistema (sys.prefix).

  • Una propria directory site-packages inizialmente vuota, destinata a ospitare le librerie di terze parti specifiche per quel determinato contesto.

  • Script di attivazione preposti alla modifica dinamica delle variabili d’ambiente della shell utente.

Tale architettura garantisce un disaccoppiamento totale: le dipendenze afferenti al Progetto A risiedono esclusivamente nella relativa directory .venv, risultando pertanto invisibili e ininfluenti rispetto al Progetto B e rispetto all’interprete di sistema.

Al fine di concretizzare i concetti esposti e fornire un riferimento visivo per l’organizzazione degli artefatti, si presentano di seguito gli schemi strutturali raccomandati. Una corretta segregazione tra codice sorgente, configurazione dell’ambiente e meta-dati del controllo versione è condizione necessaria per la manutenibilità del software.

Questa struttura riflette il workflow classico descritto nella PEP 405.

progetto_standard/
├── .git/ # Directory di sistema del versionamento (Gestita da Git)
├── .gitignore # Direttive di esclusione (CRUCIALE: deve contenere .venv/)
├── .venv/ # Ambiente Virtuale locale (IGNORATO da Git)
│ ├── bin/ # (o Scripts/ su Windows) Contiene l'interprete Python isolato
│ └── lib/ # Contiene le librerie site-packages
├── src/ # Directory radice del codice sorgente (Package Python)
│ ├── __init__.py
│ └── main.py
├── requirements.txt # Manifesto delle dipendenze (Output di pip freeze)
└── README.md # Documentazione di alto livello e istruzioni di setup

Questa struttura riflette l’adozione di toolchain moderne conformi alla PEP 518, dove la configurazione è centralizzata e dichiarativa.

progetto_moderno/
├── .git/ # Directory di sistema del versionamento
├── .gitignore # Direttive di esclusione (Gestito automaticamente da uv)
├── .python-version # Vincolo della versione Python (es. 3.12)
├── .venv/ # Ambiente Virtuale (Gestito ed effimero per uv)
├── src/ # Codice sorgente
│ └── main.py
├── pyproject.toml # Definizione dichiarativa del progetto e delle dipendenze
├── uv.lock # File di lock deterministico cross-platform (DA VERSIONARE)
└── README.md # Documentazione

Si noti come, in ambedue i modelli, la directory .venv sia rigorosamente presente nel file system locale per l’esecuzione, ma sistematicamente esclusa dal repository remoto tramite .gitignore.

Il presente approccio costituisce lo standard de facto, integrato nel linguaggio Python (in conformità alla PEP 405), ed è supportato nativamente senza la necessità di installazione di strumenti ausiliari, garantendo la massima compatibilità trasversale.

La best practice consolidata prescrive la creazione dell’ambiente virtuale all’interno della directory radice (root) del progetto. La denominazione convenzionale adottata è .venv. L’utilizzo del punto iniziale persegue molteplici finalità ergonomiche e funzionali: occulta la directory nei file system Unix-like, previene l’ingombro visivo nell’esplorazione dei file e garantisce il riconoscimento automatico da parte dei principali Ambienti di Sviluppo Integrato (IDE), quali VS Code o PyCharm, che provvederanno automaticamente a selezionare l’interprete corretto.

Comando di creazione (Specifiche Tecniche):

Finestra del terminale
# Ambiente Windows
# Il flag '-m' invoca l'esecuzione del modulo della libreria standard come script
python -m venv .venv
Finestra del terminale
# Ambiente macOS / Linux
# È sovente necessario specificare esplicitamente l'interprete python3 per disambiguazione
python3 -m venv .venv

L’attivazione non deve essere intesa come un processo arcano o una modifica permanente, bensì come una alterazione temporanea delle variabili d’ambiente della shell in uso per la sessione corrente. Nello specifico, lo script di attivazione antepone (prepends) il percorso della directory bin (o Scripts in ambiente Windows) dell’ambiente virtuale alla variabile $PATH del sistema. Inoltre, imposta la variabile VIRTUAL_ENV per indicare agli strumenti attivi la presenza dell’ambiente.

Conseguentemente, all’immissione dei comandi python o pip, il sistema operativo darà priorità agli eseguibili dell’ambiente virtuale rispetto a quelli di sistema, intercettando le chiamate.

  • Windows (PowerShell):

    Finestra del terminale
    .venv\Scripts\Activate.ps1
    # Qualora si riscontrino errori relativi alle policy di esecuzione (ExecutionPolicy), si impone il comando:
    # Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
  • Windows (CMD):

    Finestra del terminale
    .venv\Scripts\activate.bat
  • macOS / Linux (Bash/Zsh):

    Finestra del terminale
    source .venv/bin/activate

Per cessare le operazioni nell’ambiente e ripristinare il $PATH originario, è sufficiente invocare il comando deactivate, che rimuove le modifiche apportate alla sessione.

pip (Pip Installs Packages) si configura come il gestore di pacchetti ufficiale e lo strumento primario per l’interazione con il Python Package Index (PyPI). A seguito dell’attivazione dell’ambiente, ogni operazione di installazione o rimozione risulta rigorosamente confinata all’ambito locale.

  • Verifica dell’Ambiente: Preliminarmente all’installazione, è doveroso verificare quale istanza di pip sia in uso per evitare errori di contesto:

    Finestra del terminale
    which pip # Sistemi Linux/macOS
    where pip # Sistemi Windows (cmd)
    where.exe pip # Sistemi Windows (PowerShell)
    # L'output deve necessariamente puntare all'interno della directory .venv/bin o .venv\Scripts
  • Installazione dei Pacchetti:

    Finestra del terminale
    pip install pandas requests matplotlib
  • Congelamento delle Dipendenze (Freezing):

    Al fine di garantire la riproducibilità deterministica dell’ambiente su macchine terze, è imperativo generare una “istantanea” precisa delle librerie installate e delle relative versioni.

    Finestra del terminale
    pip freeze > requirements.txt

    Il file requirements.txt conterrà direttive esplicite quali pandas==2.0.3, assicurando che chiunque proceda all’installazione del progetto ottenga la medesima versione, prevenendo anomalie derivanti da aggiornamenti futuri non testati. Tuttavia, si noti che pip freeze elenca linearmente tutte le dipendenze (dirette e transitive) senza distinzione, il che può complicare la manutenzione a lungo termine rispetto ai moderni file di lock.

  • Ripristino dell’Ambiente:

    Allorquando un nuovo sviluppatore o un sistema di Continuous Integration (CI) effettui la clonazione del progetto, dovrà attenersi alla seguente procedura per replicare l’ambiente operativo:

    Finestra del terminale
    # ambiente linux/macOS
    # 1. Creazione dell'ambiente virtuale ex novo
    python3 -m venv .venv
    # 2. Attivazione del contesto
    source .venv/bin/activate
    # 3. Installazione massiva delle dipendenze
    pip install -r requirements.txt
    Finestra del terminale
    # ambiente Windows (PowerShell)
    # 1. Creazione dell'ambiente virtuale ex novo
    python -m venv .venv
    # 2. Attivazione del contesto
    .\.venv\Scripts\Activate.ps1
    # 3. Installazione massiva delle dipendenze
    pip install -r requirements.txt

3. L’Ecosistema alternativo: Anaconda e la gestione dei binari scientifici

Sezione intitolata “3. L’Ecosistema alternativo: Anaconda e la gestione dei binari scientifici”

Parallelamente all’ecosistema standard di Python, sussiste un ambiente distinto denominato Anaconda (o nella sua variante minimale, Miniconda). Tale ecosistema, sebbene basato sul medesimo linguaggio, differisce radicalmente nella filosofia di gestione dei pacchetti e degli ambienti, risultando lo standard de facto per le discipline della Data Science, del Machine Learning e del Calcolo Scientifico.

3.1 Divergenze architetturali: gestione binaria, posizionamento e Python

Sezione intitolata “3.1 Divergenze architetturali: gestione binaria, posizionamento e Python”

La differenza sostanziale tra pip/venv e conda risiede in tre aspetti fondamentali:

  1. Natura dei pacchetti:

    • Pip (Source/Wheels): Installa pacchetti Python. Se una libreria richiede estensioni C/Fortran, tenta di compilare il sorgente o scarica una “wheel” precompilata (che però potrebbe non includere tutte le dipendenze di sistema).

    • Conda (Binari Puri): Installa binari precompilati completi di tutte le dipendenze di sistema (driver CUDA, librerie MKL, compilatori GCC), eliminando la necessitĂ  di compilazione locale.

  2. Posizionamento (locale vs centralizzato):

    • Standard (venv): Crea l’ambiente localmente dentro la cartella del progetto (es. ./.venv). È effimero e legato all’esistenza della cartella di progetto.

    • Conda: Crea gli ambienti in una directory globale centralizzata (solitamente ~/anaconda3/envs/ o C:\Users\Utente.conda\envs). Gli ambienti sono disaccoppiati dal codice sorgente e sono richiamabili ovunque nel sistema tramite il loro nome.

  3. Gestione dell’Interprete:

    • Standard (venv): È vincolato alla versione di Python base installata nel sistema. Non può creare un ambiente Python 3.8 se nel sistema è installato solo Python 3.11.

    • Conda: Tratta Python come un qualsiasi altro pacchetto. Permette di installare versioni multiple e discordanti di Python (es. 3.7, 3.10, 3.12) senza toccare il sistema operativo.

Di seguito si illustrano i comandi essenziali per la gestione del ciclo di vita di un ambiente Conda. Si noti come l’attivazione avvenga tramite nome e non tramite percorso.

Creazione dell’Ambiente:

Finestra del terminale
# Sintassi: conda create --name <nome_ambiente> python=<versione>
conda create --name progetto_ds python=3.9

Attivazione e gestione:

Finestra del terminale
# Attivazione (Cross-platform) - Nota: si usa il NOME, non il percorso
conda activate progetto_ds
# Installazione Pacchetti (dal repository Anaconda o conda-forge)
conda install numpy pandas matplotlib scikit-learn
# Esportazione dell'ambiente (equivalente a requirements.txt ma piĂš completo)
conda env export > environment.yml
# Ricostruzione dell'ambiente da file YAML
conda env create -f environment.yml

La scelta tra l’approccio standard (venv) e Conda deve essere guidata dalle specifiche esigenze del progetto:

  • Si raccomanda l’uso di Conda/Miniconda se:

    • Si opera in ambiti scientifici (Data Science, AI) ove le dipendenze da librerie C/Fortran/CUDA sono pervasive.

    • Si utilizza il sistema operativo Windows, ove la compilazione di estensioni C risulta complessa.

    • Si necessita di cambiare frequentemente la versione dell’interprete Python tra diversi progetti.

  • Si raccomanda l’uso di venv (o uv) se:

    • Si sviluppano applicazioni Web (Django, FastAPI) destinate al deployment in container Docker o su cloud (PaaS).

    • Si predilige un ambiente leggero e strettamente aderente agli standard della Python Software Foundation.

    • Si opera su sistemi Linux/macOS ove la gestione dei compilatori è nativa.

4. Approfondimento tecnico: compilazione locale di dipendenze scientifiche (Caso Studio AI)

Sezione intitolata “4. Approfondimento tecnico: compilazione locale di dipendenze scientifiche (Caso Studio AI)”

Esistono scenari avanzati in cui l’uso di Conda non è possibile o desiderabile (es. vincoli di licenza, necessità di versioni bleeding-edge da Git, o integrazione in pipeline CI/CD standard). In questi casi, è necessario colmare il divario tra pip (che si aspetta compilatori presenti) e il sistema operativo.

Caso di Studio: Installazione di llama-cpp-python per l’esecuzione locale di LLM (Large Language Models) con accelerazione hardware. Questa libreria è un binding Python per llama.cpp (scritto in C++) e richiede tassativamente la compilazione locale per abilitare il supporto GPU (CUDA/Metal).

Prima di attivare il venv e lanciare pip, è imperativo preparare il sistema ospite con i compilatori C/C++ e gli header di Python.

È necessario installare il meta-pacchetto build-essential e, crucialmente, gli header di sviluppo di Python (python3-dev), senza i quali la compilazione fallirà con errore Python.h: No such file.

Finestra del terminale
# Aggiornamento e installazione toolchain
sudo apt update
sudo apt install build-essential python3-dev cmake
# Se si usa GPU NVIDIA, assicurarsi che il CUDA Toolkit sia installato (comando nvcc)
nvcc --version

Windows non possiede compilatori nativi nel PATH. È necessario installare Visual Studio Build Tools (da non confondere con VS Code).

  1. Scaricare l’installer dal sito Microsoft.

  2. Selezionare il workload “Sviluppo desktop con C++”.

  3. Assicurarsi che siano selezionati: “MSVC compiler”, “Windows 10/11 SDK” e “CMake tools for Windows”.

Una volta predisposti i compilatori, la procedura di installazione differisce da quella standard perché dobbiamo passare istruzioni al compilatore sottostante (CMake) tramite variabili d’ambiente.

  1. Creazione e attivazione venv

    Finestra del terminale
    # Windows
    python -m venv .venv
    .venv\Scripts\activate
    Finestra del terminale
    # Linux/macOS
    python3 -m venv .venv
    source .venv/bin/activate
  2. Installazione delle dipendenze di build

    Il pacchetto richiede scikit-build-core o cmake python-side per orchestrare la compilazione.

    Finestra del terminale
    pip install --upgrade pip setuptools wheel cmake scikit-build-core
  3. Compilazione e Installazione (Linux con CUDA)

    Per abilitare l’accelerazione GPU, impostiamo CMAKE_ARGS. Usiamo l’opzione --no-binary per forzare pip a ignorare eventuali wheel precompilate (che solitamente sono solo CPU) e compilare da zero.

    Finestra del terminale
    # Imposta le variabili per abilitare CuBLAS (backend NVIDIA)
    CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python --upgrade --force-reinstall --no-binary llama-cpp-python
  4. Compilazione e Installazione (Windows con CUDA)

    Su PowerShell, la sintassi per le variabili d’ambiente differisce:

    Finestra del terminale
    # Imposta le variabili per abilitare CuBLAS (backend NVIDIA)
    $env:CMAKE_ARGS = "-DLLAMA_CUBLAS=on"
    pip install llama-cpp-python --upgrade --force-reinstall --no-binary llama-cpp-python

Al termine, pip avrà generato un binario .so (Linux) o .pyd (Windows) specificamente ottimizzato per l’hardware locale, collegato (linked) staticamente o dinamicamente alle librerie CUDA di sistema.

A compilazione avvenuta con successo, la libreria è importabile nel contesto Python come un normale pacchetto. Di seguito si riporta un esempio minimale di inferenza che sfrutta l’accelerazione hardware configurata.

from llama_cpp import Llama
# Inizializzazione del modello
# Nota: È necessario scaricare un modello in formato GGUF (es. da HuggingFace)
# n_gpu_layers=-1 sposta tutti i layer sulla GPU se VRAM sufficiente
llm = Llama(
model_path="./models/mistral-7b-instruct-v0.2.Q4_K_M.gguf",
n_gpu_layers=-1,
verbose=True
)
# Esecuzione dell'inferenza
output = llm(
"Q: Qual è la capitale della Francia? A: ",
max_tokens=32,
stop=["Q:", "\n"],
echo=True
)
print(output)

4.5 Analisi comparativa: l’approccio “Zero-Compilation” di Conda

Sezione intitolata “4.5 Analisi comparativa: l’approccio “Zero-Compilation” di Conda”

Al fine di offrire una prospettiva completa e in accordo con i principi di efficienza operativa, si illustra come la medesima libreria possa essere integrata sfruttando l’ecosistema Conda, eliminando la necessità di compilazione locale.

Grazie all’architettura basata su binari pre-compilati (descritta nella Sezione 3.1), i manutentori di repository come conda-forge provvedono a generare artefatti software già compilati e ottimizzati per le diverse combinazioni di hardware (CPU/GPU) e sistemi operativi.

Scenario Alternativo:

In luogo della complessa procedura di configurazione dei build tools (VS C++, CMake, NVCC) descritta nei paragrafi 4.1-4.3, l’adozione di Conda riduce l’intero processo a una singola istruzione dichiarativa, delegando al package manager la risoluzione delle dipendenze di basso livello.

Finestra del terminale
# 1. Creazione di un ambiente dedicato (Best Practice)
conda create --name ai_env python=3.10
# 2. Attivazione dell'ambiente
conda activate ai_env
# 3. Installazione del pacchetto precompilato
# Il canale 'conda-forge' fornisce binari aggiornati.
# Nota: Conda gestirĂ  automaticamente le dipendenze di runtime.
conda install -c conda-forge llama-cpp-python

Nota Tecnica: Sebbene questo approccio offra un’immediata operatività (“It just works”), potrebbe talvolta fornire versioni leggermente meno recenti rispetto al codice sorgente disponibile su GitHub, o configurazioni generiche non ottimizzate per set di istruzioni CPU specifici (es. AVX512) che invece una compilazione locale mirata potrebbe sfruttare. Tuttavia, per la maggioranza degli scenari applicativi, il risparmio in termini di tempo di configurazione e manutenzione rende Conda la scelta pragmatica preferibile.

5. L’ecosistema Interactive Computing: Jupyter Notebooks e Gestione dei Kernel

Sezione intitolata “5. L’ecosistema Interactive Computing: Jupyter Notebooks e Gestione dei Kernel”

L’uso di notebook interattivi (file .ipynb) rappresenta uno standard industriale nella Data Science e nell’AI. Tuttavia, l’integrazione tra Jupyter (l’ambiente di interfaccia) e gli ambienti virtuali (dove risiedono le librerie) è spesso fonte di confusione. È fondamentale comprendere il concetto di Kernel.

Un errore comune è pensare che, avviando jupyter notebook all’interno di un ambiente virtuale attivo, il notebook usi automaticamente quell’ambiente. Non è sempre così. Jupyter è progettato con un’architettura client-server disaccoppiata:

  • L’Interfaccia (Frontend): È l’applicazione web (JupyterLab o Jupyter Notebook) che mostra il codice e i grafici.

  • Il Kernel (Backend): È il processo separato che esegue effettivamente il codice Python.

È possibile (e frequente) avere l’interfaccia Jupyter installata nell’ambiente “base” o tramite pipx, mentre i vari notebook eseguono codice in diversi ambienti virtuali (venv o conda) configurati come kernel distinti.

Per rendere visibile un ambiente virtuale standard (venv) a Jupyter, è necessario installare al suo interno il pacchetto ipykernel e registrarlo manualmente.

Procedura Operativa:

Finestra del terminale
# 1. Attivazione del venv di progetto
# in linux/macOS
source .venv/bin/activate
# oppure in Windows (PowerShell)
.venv\Scripts\Activate.ps1
# 2. Installazione del supporto kernel
pip install ipykernel
# 3. Registrazione del kernel (User-level)
# --name: identificativo interno (no spazi)
# --display-name: nome visibile nel menu di Jupyter
python -m ipykernel install --user --name=mio_progetto_env --display-name "Python (Mio Progetto)"

Dopo questa operazione, nel menu “Kernel” -> “Change Kernel” di Jupyter comparirà la voce “Python (Mio Progetto)”, permettendo di eseguire il codice usando le librerie di quel specifico venv.

L’ecosistema Conda offre un livello di automazione superiore per la gestione dei kernel, eliminando la necessità di registrazione manuale descritta sopra.

Il pacchetto nb_conda_kernels:

Installando questa estensione nell’ambiente dove risiede Jupyter (solitamente l’ambiente base o un ambiente dedicato agli strumenti), essa rileverà automaticamente tutti gli altri ambienti Conda presenti nel sistema che abbiano il pacchetto ipykernel installato.

Workflow consigliato (Conda):

  1. Ambiente “Jupyter” (Contenitore):

    Si crea un ambiente dedicato per l’interfaccia grafica e le estensioni.

    Finestra del terminale
    conda create --name jupyter_env jupyterlab nb_conda_kernels
  2. Ambiente “Progetto” (Esecuzione):

    Si crea l’ambiente di lavoro specifico per l’analisi dati.

    Finestra del terminale
    conda create --name analisi_dati python=3.9 pandas scikit-learn ipykernel

    Nota: È cruciale installare ipykernel anche nell’ambiente target.

  3. Utilizzo:

    Avviando Jupyter dall’ambiente contenitore:

    Finestra del terminale
    conda activate jupyter_env
    jupyter lab

    La dashboard mostrerà automaticamente un kernel denominato Python [conda env:analisi_dati]. Non è richiesta alcuna registrazione manuale: creando nuovi ambienti Conda (con ipykernel), questi appariranno dinamicamente nella lista.

Mentre venv e conda sono progettati per isolare le librerie necessarie allo sviluppo di un progetto (importabili nel codice), esiste una classe di strumenti Python destinati all’uso come applicazioni da riga di comando (CLI) stand-alone, da richiamare ovunque nel sistema.

L’installazione di strumenti come black o poetry mediante pip install --user (globale) espone al rischio di conflitti di dipendenze (e.g., due tool richiedono versioni diverse della stessa libreria). pipx risolve questo problema mediante una rigorosa segregazione architetturale.

Dove vengono installati i pacchetti?

A differenza di pip, che installa tutto in un’unica directory site-packages condivisa, pipx adotta la seguente struttura:

  1. Ambienti Virtuali Dedicati: Per ogni applicazione installata, pipx crea automaticamente un ambiente virtuale separato. La posizione predefinita di questi ambienti è:

    • Linux/macOS: ~/.local/pipx/venvs/

    • Windows: %USERPROFILE%.local\pipx\venvs\

  2. Esposizione dei binari (Shim/Symlink): I soli file eseguibili (binari) vengono collegati simbolicamente o tramite “shim” in una directory inclusa nel $PATH dell’utente (tipicamente ~/.local/bin), rendendoli invocabili da terminale senza dover attivare alcun ambiente.

In sintesi: ogni tool ha il suo ambiente privato, ma l’utente percepisce un unico comando globale.

L’adozione di pipx è fortemente raccomandata per le seguenti categorie di software:

Installare poetry tramite pip globale può causare conflitti se un progetto richiede librerie che poetry stesso usa internamente.

  • Comando: pipx install poetry

  • Beneficio: Poetry gira nel suo ambiente isolato. Se si aggiorna Poetry, non si rischia di rompere altri tool.

B. Strumenti di Code Quality e Formatting (Black, Ruff, Flake8)

Sezione intitolata “B. Strumenti di Code Quality e Formatting (Black, Ruff, Flake8)”

Questi strumenti devono essere eseguiti su molteplici progetti diversi. Installarli in ogni singolo venv di progetto è ridondante; installarli globalmente è rischioso.

  • Comando: pipx install black

  • Beneficio: Si può digitare black . in qualsiasi cartella del sistema. pipx garantisce che black abbia sempre le sue dipendenze soddisfatte indipendentemente dal progetto su cui sta lavorando.

C. Utility di Sistema e Networking (Httpie, Glances, YouTube-DL)

Sezione intitolata “C. Utility di Sistema e Networking (Httpie, Glances, YouTube-DL)”

Applicazioni che sostituiscono tool di sistema (come curl o top).

  • Comando: pipx install httpie

  • Uso: Esecuzione del comando http o https direttamente da terminale per testare API.

Per evitare di installare Jupyter in ogni progetto, si può usare pipx per gestire il core di Jupyter e collegare i kernel dei vari venv.

  • Comando: pipx install jupyterlab

6.3 FunzionalitĂ  avanzata: Dependency Injection (pipx inject)

Sezione intitolata “6.3 Funzionalità avanzata: Dependency Injection (pipx inject)”

Talvolta un’applicazione isolata in pipx necessita di librerie aggiuntive (plugin) per funzionare. Poiché l’ambiente è chiuso, non è possibile usare pip install. Si usa inject.

  • Scenario: Si vuole usare mkdocs (generatore di documentazione) con il tema mkdocs-material.

  • Procedura:

    1. Installare l’app principale: pipx install mkdocs

    2. Iniettare la dipendenza nello stesso ambiente: pipx inject mkdocs mkdocs-material

Finestra del terminale
# 1. Installazione di pipx (operazione una tantum)
python -m pip install --user pipx
python -m pipx ensurepath # Aggiunge ~/.local/bin al PATH
# 2. Installazione di un tool (es. il linter ruff)
pipx install ruff
# Output: installed package ruff 0.1.0, Python 3.10.12
# These apps are now globally available:
# - ruff
# 3. Aggiornamento di tutti i tool installati
pipx upgrade-all
# 4. Esecuzione Ephemera (Senza installazione)
# Scarica, esegue e poi cancella l'ambiente temporaneo. Utile per test rapidi.
pipx run cowsay "Pipx è architetturalmente superiore"

Con l’introduzione di uv, l’ecosistema Python dispone di un meccanismo estremamente performante per l’esecuzione di strumenti CLI in modalita’ effimera: uvx. Dal punto di vista semantico, uvx e’ un alias esatto di uv tool run; in altri termini, i seguenti comandi sono equivalenti:

Finestra del terminale
uvx ruff check .
uv tool run ruff check .

L’elemento architetturalmente rilevante consiste nel fatto che uvx non installa stabilmente il tool nel progetto e non contamina l’ambiente virtuale corrente. Invece, crea o riusa un ambiente isolato nella cache di uv, esegue il comando richiesto e tratta tale ambiente come un artefatto temporaneo e ricreabile, cioe’ privo di valore permanente. Se la cache viene rimossa, uvx ricostruira’ automaticamente l’ambiente alla successiva esecuzione.

Questo modello lo rende particolarmente adatto per:

  • esecuzioni occasionali di formatter, linter e generatori di progetto;

  • test rapidi di utility CLI senza installazione persistente;

  • uso di versioni temporanee o specifiche di un tool;

  • ambienti CI/CD in cui si desidera evitare installazioni globali permanenti.

Poiche’ nella pratica questi strumenti vengono spesso confusi, e’ opportuno distinguerne con rigore i ruoli:

StrumentoScopo primarioPersistenzaRelazione col progetto corrente
pipx installInstallare tool CLI globali in ambienti isolatiPersistenteNessuna integrazione col progetto
pipx runEseguire una CLI in modo temporaneoEffimeraNessuna integrazione col progetto
uvxEseguire tool Python in ambienti effimeri con cache ad alte prestazioniEffimera/cachedSempre isolato dal progetto
uv runEseguire comandi nel contesto del progettoDipende dal progetto .venvIntegra dipendenze e lockfile del progetto

La regola operativa puo’ essere formulata come segue:

  • usare uvx quando il tool va eseguito come strumento esterno e non deve dipendere dal progetto;

  • usare uv run quando il tool deve vedere il package corrente, le dipendenze dichiarate nel pyproject.toml o l’ambiente lockato del repository;

  • usare uv tool install o pipx install quando si vuole che il comando sia disponibile stabilmente nel PATH.

Un esempio chiarificatore:

  • uvx ruff check . e’ ideale per lanciare Ruff come utility esterna;

  • uv run pytest e’ preferibile a uvx pytest, poiche’ i test devono normalmente importare il codice del progetto e usare le sue dipendenze dichiarate.

Quando uvx esegue un tool, uv risolve il pacchetto richiesto, costruisce un ambiente dedicato e lo conserva nella cache globale. Tale cache ha finalita’ prestazionali: evita di scaricare e reinstallare il medesimo tool a ogni invocazione. Tuttavia, la cache non equivale a installazione permanente.

Le implicazioni pratiche sono:

  1. La prima esecuzione puo’ richiedere il download del pacchetto.

  2. Le esecuzioni successive risultano normalmente molto piu’ rapide.

  3. Se si esegue uv cache clean, l’ambiente del tool viene eliminato e verra’ ricreato al bisogno.

  4. Se un tool e’ gia’ stato installato con uv tool install, uvx tende a usare quella versione installata, salvo richiesta esplicita di un’altra versione o uso del flag --isolated.

Per ispezionare la cache di uv:

Finestra del terminale
uv cache dir
uv cache size

Per forzare un comportamento senza cache:

Finestra del terminale
uvx --no-cache ruff check .

Tale opzione e’ utile in debugging, benchmarking o flussi altamente deterministici, ma riduce il vantaggio prestazionale della cache.

Finestra del terminale
uvx ruff check .
uvx black --check .
uvx http --help

Nel primo esempio uvx deduce che il comando ruff corrisponde al pacchetto Python ruff. Questa inferenza e’ corretta per molti tool, ma non per tutti.

B. Caso in cui nome del pacchetto e nome del comando differiscono

Sezione intitolata “B. Caso in cui nome del pacchetto e nome del comando differiscono”

Non sempre il nome del package su PyPI coincide con l’eseguibile. Un caso classico e’ httpie, che espone i comandi http e https.

Finestra del terminale
uvx --from httpie http GET https://example.org

Il flag --from permette di indicare esplicitamente quale pacchetto installare per ottenere il comando desiderato.

C. Richiesta di una versione esatta o dell’ultima disponibile

Sezione intitolata “C. Richiesta di una versione esatta o dell’ultima disponibile”

Per riprodurre una specifica versione:

Finestra del terminale
uvx ruff@0.6.9 --version
uvx pyright@1.1.390 --version

Per forzare l’uso dell’ultima release disponibile e aggiornare la cache associata:

Finestra del terminale
uvx ruff@latest check .

Per vincoli piu’ complessi si usa --from:

Finestra del terminale
uvx --from 'ruff>=0.6,<0.7' ruff check .
uvx --from 'httpie>3,<4' http --version

Talvolta il tool necessita di estensioni opzionali. In questo scenario, uvx supporta --with.

Finestra del terminale
uvx --with mkdocs-material mkdocs serve
uvx --with pytest-cov pytest --cov=src

In tal modo il tool principale e le dipendenze supplementari vengono risolti nello stesso ambiente effimero. Questo pattern e’ particolarmente utile per ecosistemi plugin-based.

Se il tool supporta extras dichiarati dal package, si puo’ ricorrere ancora a --from.

Finestra del terminale
uvx --from 'mypy[faster-cache,reports]==1.13.0' mypy src --xml-report build/mypy-report

Questo approccio e’ piu’ rigoroso rispetto all’installazione globale casuale di plugin, poiche’ rende esplicita l’intera superficie di dipendenze richiesta dal comando.

uvx puo’ eseguire un tool direttamente da repository Git, tag, branch o commit.

Finestra del terminale
uvx --from git+https://github.com/httpie/cli httpie --version
uvx --from git+https://github.com/httpie/cli@3.2.4 http --version
uvx --from git+https://github.com/httpie/cli@master http --version

Questo e’ utile per testare:

  • una patch non ancora pubblicata su PyPI;

  • una release candidate;

  • una branch sperimentale di un tool.

Ogni ambiente creato da uvx e’ associato a uno specifico interprete Python. Se necessario, e’ possibile richiedere una versione esplicita tramite --python.

Finestra del terminale
uvx --python 3.10 ruff check .
uvx --python 3.12 pyright
uvx --python 3.13 black .

Questa capacita’ risulta importante quando:

  • si desidera verificare la compatibilita’ di un tool con una certa major/minor di Python;

  • il tool ha requisiti minimi non soddisfatti dal Python di sistema;

  • si vogliono uniformare gli ambienti operativi tra workstation e CI.

Inoltre, uvx puo’ essere usato perfino per eseguire un interprete Python isolato:

Finestra del terminale
uvx python
uvx python@3.12

Si tratta di un meccanismo molto utile per test temporanei o per verificare rapidamente la disponibilita’ di una determinata versione senza allestire manualmente un ambiente virtuale persistente.

6.10 Modalita’ isolata, refresh e controllo del comportamento

Sezione intitolata “6.10 Modalita’ isolata, refresh e controllo del comportamento”

Se un tool e’ gia’ installato in modo persistente con uv tool install, uvx puo’ riusarlo. Se pero’ si desidera ignorare ogni installazione persistente e forzare un ambiente autonomo, si usa --isolated.

Finestra del terminale
uvx --isolated ruff --version

Questo comportamento e’ utile per:

  • confrontare una versione installata con una effimera;

  • evitare interferenze con tool gia’ presenti nel sistema;

  • validare pipeline di build o documentazione in condizioni pulite.

Per forzare l’aggiornamento del contenuto cache-izzato:

Finestra del terminale
uvx --refresh ruff check .
uvx --refresh-package ruff ruff check .

Per operare senza accesso alla rete, riusando solo cio’ che e’ gia’ presente localmente:

Finestra del terminale
uvx --offline ruff check .

Questa modalita’ e’ appropriata in contesti air-gapped o per verificare la completezza della cache in pipeline riproducibili.

A. Formattazione o linting occasionale di repository eterogenei

Sezione intitolata “A. Formattazione o linting occasionale di repository eterogenei”

Se si lavora su molteplici repository ma non si desidera installare permanentemente ogni tool:

Finestra del terminale
uvx ruff check .
uvx black .
uvx pyright

Questo flusso riduce l’overhead di setup e impedisce la proliferazione di installazioni globali disallineate.

Finestra del terminale
uvx --with mkdocs-material mkdocs build

Caso ideale quando si vuole generare documentazione una tantum senza dover mantenere un ambiente locale dedicato al solo tool di build.

Finestra del terminale
uvx --from httpie http GET https://api.github.com/repos/astral-sh/uv

Questo approccio e’ utile per demo in aula, troubleshooting API o test manuali veloci.

D. Esecuzione di tool sperimentali o non ancora adottati stabilmente

Sezione intitolata “D. Esecuzione di tool sperimentali o non ancora adottati stabilmente”
Finestra del terminale
uvx cookiecutter --help
uvx copier --help
uvx hatch --help

Prima di scegliere se adottare un tool nel workflow stabile, e’ possibile valutarlo senza installazione persistente.

Finestra del terminale
uvx ruff check .
uvx pytest --version
uvx --from 'mypy==1.13.0' mypy src

In pipeline temporanee, uvx permette di evitare configurazioni globali permanenti e rende esplicito quale comando viene richiesto in ogni step.

Si raccomanda uvx quando:

  • il tool e’ usato saltuariamente o in modo non continuativo;

  • si desidera evitare installazioni globali permanenti;

  • e’ utile provare versioni diverse dello stesso strumento;

  • si vuole un’esecuzione rapida, isolata e facilmente ripetibile.

Si raccomanda invece uv tool install o pipx install quando:

  • il comando deve essere sempre disponibile nel PATH senza prefisso uvx;

  • altri script o processi esterni si aspettano che il binario sia installato stabilmente;

  • si vuole gestire il ciclo di vita del tool come installazione persistente.

Si raccomanda uv run, e non uvx, quando:

  • il comando deve operare dentro il contesto del progetto;

  • il tool deve importare il package locale;

  • il risultato deve essere coerente con pyproject.toml, uv.lock e .venv del repository.

In sintesi, uvx rappresenta la soluzione di nuova generazione per il concetto di “tool eseguito al bisogno”: unisce l’isolamento tipico di pipx run alla velocita’, alla cache globale e alla coerenza ecosistemica offerte da uv.

7. Integrazione con i Sistemi di Controllo Versione (Git e GitHub)

Sezione intitolata “7. Integrazione con i Sistemi di Controllo Versione (Git e GitHub)”

Il controllo di versione costituisce un requisito essenziale per qualsiasi progetto software moderno; tuttavia, esso richiede una rigorosa disciplina operativa onde evitare il commit accidentale di file binari, artefatti temporanei o contenenti dati sensibili, che potrebbero compromettere la pulizia e la sicurezza del repository.

È fatto divieto assoluto di includere la directory .venv (o le directory degli ambienti Conda locali) nel repository Git, in alcuna circostanza. Tale pratica è considerata un grave errore procedurale per i seguenti motivi:

  1. Ridondanza Dimensionale: Una directory .venv media contiene migliaia di file e svariati megabyte di dati ridondanti, replicabili facilmente tramite requirements.txt o environment.yml.

  2. Assenza di Portabilità: I file binari (e.g., interprete Python, librerie C compilate quali numpy o psycopg2) sono strettamente specifici per il Sistema Operativo e l’architettura della CPU (x86 vs ARM). Un ambiente creato su macOS risulterebbe totalmente inoperabile su Windows o Linux.

  3. Sicurezza e Privacy: La directory potrebbe contenere script con percorsi assoluti riferiti alla macchina locale (hardcoded paths) o file di configurazione con credenziali non sanificate.

Ciò che deve essere sottoposto a versionamento è la “ricetta” dichiarativa per la ricostruzione dell’ambiente (requirements.txt, pyproject.toml o environment.yml), non l’ambiente medesimo.

7.2 Strategie e metodologie per la generazione del file .gitignore

Sezione intitolata “7.2 Strategie e metodologie per la generazione del file .gitignore”

La redazione manuale ex novo di un file .gitignore è un’operazione propensa all’errore e sconsigliata. Esistono molteplici approcci, dalla generazione via interfaccia web all’automazione completa tramite CLI, che garantiscono la creazione di file di esclusione completi e robusti.

Questo metodo è ideale per chi preferisce strumenti visuali e desidera combinare rapidamente configurazioni per diversi sistemi operativi e IDE.

  • Strumento: gitignore.io

  • Procedura:

    1. Accedere al sito.

    2. Digitare i tag rilevanti nel campo di ricerca (es. “Python”, “Windows”, “Linux”, “macOS”, “Visual Studio Code”).

    3. Scaricare il file generato e salvarlo nella root del progetto come .gitignore.

7.2.2 Approccio B: Generazione via riga di comando (CLI universale)

Sezione intitolata “7.2.2 Approccio B: Generazione via riga di comando (CLI universale)”

Questo metodo sfrutta l’API di gitignore.io direttamente dal terminale, utilizzando comandi standard come curl disponibili su tutti i sistemi operativi moderni. È la soluzione prediletta per l’automazione.

Finestra del terminale
# Comando universale (bash/zsh/powershell)
curl -L https://www.toptal.com/developers/gitignore/api/python,windows,linux,macos,visualstudiocode,jetbrains > .gitignore

7.2.3 Approccio C: Generazione tramite strumenti Python dedicati (CLI specifica)

Sezione intitolata “7.2.3 Approccio C: Generazione tramite strumenti Python dedicati (CLI specifica)”

Per flussi di lavoro puramente Python, è possibile utilizzare pacchetti dedicati come ignr che offrono interfacce CLI piÚ amichevoli rispetto a curl.

Finestra del terminale
# 1. Installazione (preferibilmente con pipx)
pipx install ignr
# 2. Generazione del file
ignr -p python > .gitignore

7.2.4 Approccio D: Generazione automatica (Scaffolding moderno)

Sezione intitolata “7.2.4 Approccio D: Generazione automatica (Scaffolding moderno)”

Strumenti di nuova generazione come uv (più avanti sono mostrati i dettagli e l’installazione) o poetry includono la creazione automatica di un .gitignore ottimizzato al momento dell’inizializzazione del progetto.

Finestra del terminale
# Creazione progetto con uv
uv init nome_progetto
# Risultato: Genera struttura cartelle + pyproject.toml + .python-version + .gitignore corretto

Qualora si renda necessaria la creazione manuale o la verifica di un file esistente, si riporta di seguito un modello di riferimento che copre la quasi totalitĂ  delle casistiche per un progetto Python moderno.

# --- Byte-compiled / optimized / DLL files ---
__pycache__/
*.py[cod]
*$py.class
# --- C extensions ---
*.so
# --- Distribution / packaging ---
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# --- Virtual Environments (CRUCIALE) ---
# Nomi comuni per i venv che devono essere tassativamente ignorati
venv/
env/
ENV/
env.bak/
venv.bak/
.venv/
# --- Unit test / coverage reports ---
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# --- Jupyter Notebook ---
.ipynb_checkpoints
# --- Ambienti IDE (Opzionale ma Raccomandato) ---
.vscode/
.idea/
# --- Environment variables (SICUREZZA: MAI caricare chiavi segrete!) ---
.env

Procedura dettagliata per l’inizializzazione di un repository incontaminato e conforme alle best practices:

  1. Setup Locale:

    Finestra del terminale
    git init
    # VERIFICA PRELIMINARE CRUCIALE: Accertarsi che .venv sia ignorato (e.g., tramite 'git status')
    # La cartella non deve apparire tra i file 'Untracked'.
    # Solo successivamente eseguire l'aggiunta dei file.
    git add .
    git commit -m "Initial commit: project scaffolding and configuration"
  2. Strategia di Branching (Cenni):

    È buona norma astenersi dall’operare direttamente sul branch main, preferendo l’uso di feature branches.

    Finestra del terminale
    git checkout -b feature/setup-ambiente
    # ... esecuzione modifiche ...
    git commit -m "Add dependency management configuration"
  3. Collegamento Remoto:

    Finestra del terminale
    git branch -M main
    git remote add origin https://github.com/UTENTE/NOME_REPO.git
    git push -u origin main

Introdotto nel 2024, uv rappresenta un cambiamento paradigmatico e un salto evolutivo nel packaging Python. Sviluppato in Rust da Astral (creatori del linter ruff), è progettato per offrire prestazioni superiori da 10 a 100 volte rispetto a pip e pip-tools, unificando funzionalità che precedentemente richiedevano l’impiego di molteplici strumenti distinti (gestione versioni Python, gestione venv, gestione dipendenze).

  • VelocitĂ  estrema: La risoluzione delle dipendenze avviene in tempi pressochĂŠ istantanei, grazie a un resolver in Rust altamente ottimizzato e parallelizzato.

  • Gestione unificata e cache globale: Amministra versioni di Python (installandole se mancanti), ambienti virtuali, dipendenze e strumenti globali, utilizzando una cache centralizzata che evita il riscaricamento dei pacchetti per progetti diversi.

  • File di lock multipiattaforma: Genera un file uv.lock che garantisce installazioni rigorosamente deterministiche su qualsivoglia sistema operativo, risolvendo una limitazione storica del comando pip freeze che spesso produceva output specifici per la piattaforma di generazione.

A differenza di altri strumenti Python che dipendono dall’interprete di sistema, uv è distribuito come singolo binario compilato staticamente, il che ne facilita l’installazione indipendente.

Metodo Raccomandato (Script Standalone):

Questo metodo garantisce che uv si aggiorni autonomamente e non entri in conflitto con i pacchetti Python installati.

  • Linux / macOS:

    Finestra del terminale
    curl -LsSf https://astral.sh/uv/install.sh | sh
  • Windows (PowerShell):

    Finestra del terminale
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Metodo Alternativo (PyPI):

Sebbene possibile, l’installazione via pip è sconsigliata per l’uso sistemico poiché lega uv a uno specifico interprete Python.

Finestra del terminale
pip install uv

Verifica:

Finestra del terminale
uv --version
# Output atteso: uv 0.x.x

Aggiornamento di uv:

Finestra del terminale
uv self update

uv non adotta requirements.txt come prima scelta (sebbene lo supporti), bensĂŹ lo standard moderno definito nella PEP 518: il file pyproject.toml.

Finestra del terminale
# Inizializzazione di un nuovo progetto con scaffolding automatico
uv init nome_progetto
cd nome_progetto

Questo comando genera:

  • pyproject.toml: Metadati del progetto e dipendenze.

  • .python-version: Versione Python vincolata per il progetto.

  • .gitignore: Configurazione Git di base.

  • main.py: Script di esempio.

Il comando crea automaticamente un ambiente virtuale .venv in background non appena necessario (es. al primo uv sync o uv run). uv gestisce automaticamente il download e l’installazione della versione Python richiesta se non presente nel sistema.

Aggiungere dipendenze:

Finestra del terminale
# Aggiunta di pacchetti (aggiorna pyproject.toml e uv.lock)
uv add pandas requests
# Aggiunta di dipendenze di sviluppo
uv add --dev pytest ruff

Rimuovere dipendenze:

Finestra del terminale
# Rimozione di pacchetti (aggiorna pyproject.toml e uv.lock)
uv remove requests

Migrazione da requirements.txt:

Se si possiede un progetto esistente, è possibile importare le dipendenze nel nuovo formato:

Finestra del terminale
uv add -r requirements.txt

Sincronizzazione (uv sync):

Allinea lo stato dell’ambiente virtuale .venv con il file di lock uv.lock. È idempotente: rimuove pacchetti superflui e installa quelli mancanti.

Finestra del terminale
uv sync

Aggiornamento (uv lock --upgrade):

Aggiorna le versioni dei pacchetti nel file di lock rispettando i vincoli di pyproject.toml.

Finestra del terminale
# Aggiorna tutte le dipendenze ai limiti consentiti
uv lock --upgrade
# Applica le modifiche all'ambiente
uv sync

uv consente l’esecuzione di script con dipendenze “al volo”, senza la necessità di attivare manualmente l’ambiente.

Finestra del terminale
# Esegue lo script usando l'ambiente del progetto
uv run main.py
# Esegue uno script con dipendenze temporanee (senza installarle nel progetto)
uv run --with pandas script_analisi.py

9. Ingegneria della qualitĂ  del codice: Analisi Statica e Formattazione

Sezione intitolata “9. Ingegneria della qualità del codice: Analisi Statica e Formattazione”

L’adozione di rigorosi standard di qualità del codice (linting) e di formattazione automatica (formatting) non è opzionale nello sviluppo professionale moderno. Questi strumenti prevengono bug logici, uniformano lo stile tra diversi sviluppatori e riducono drasticamente il tempo dedicato alla code review.

Attualmente, l’ecosistema Python sta vivendo una transizione tra una toolchain classica (frammentata e basata su Python) e una toolchain moderna (unificata e basata su Rust).

CaratteristicaToolchain Classica (Python-based)Toolchain Moderna (Ruff)
ComponentiBlack (Formattazione), isort (Ordinamento import), Pylint (Linting profondo), Flake8 (Linting stilistico)Ruff (Tutto incluso)
PrestazioniLente (secondi/minuti su codebase grandi). Single-core per default.Estreme (millisecondi). Scritto in Rust, parallelizzato nativamente.
ConfigurazioneFrammentata (multipli file o sezioni diverse nel pyproject.toml). Conflitti frequenti tra Black e isort/Flake8.Centralizzata in un’unica sezione [tool.ruff]. Compatibilità garantita by design.
ManutenzioneRichiede l’installazione e l’aggiornamento di 4+ pacchetti separati.Un solo binario, una sola dipendenza.

Prestazioni: In benchmark su codebase come CPython o Airflow, Ruff risulta dai 10 ai 100 volte piĂš veloce della combinazione Flake8 + Black.

9.2 Configurazione A: Workflow Moderno con Ruff (Raccomandato)

Sezione intitolata “9.2 Configurazione A: Workflow Moderno con Ruff (Raccomandato)”

Ruff sostituisce Black, isort, Flake8 e gran parte di Pylint.

Ruff può essere installato come tool globale (via pipx) per l’uso da terminale, o aggiunto alle dipendenze di sviluppo del progetto.

Finestra del terminale
# Installazione globale (per uso CLI ovunque)
pipx install ruff
# Aggiunta al progetto (con uv o pip)
uv add --dev ruff
# oppure
pip install ruff

Per abilitare la formattazione e l’ordinamento degli import, è necessario configurare esplicitamente Ruff nel pyproject.toml.

[tool.ruff]
# Lunghezza riga (compatibile con Black)
line-length = 88
target-version = "py310"
[tool.ruff.lint]
# Abilita regole:
# E, W: Errori e Warning standard (simili a Flake8)
# F: Pyflakes
# I: isort (ordinamento import)
# B: Flake8-bugbear (bug comuni)
# UP: Pyupgrade (modernizzazione sintassi)
select = ["E", "W", "F", "I", "B", "UP"]
ignore = []
[tool.ruff.format]
# Usa lo stile delle virgolette doppie (come Black)
quote-style = "double"
# Indenta con spazi
indent-style = "space"

Per integrare Ruff in VS Code, installare l’estensione ufficiale Ruff (identificativo: charliermarsh.ruff).

Di seguito la configurazione consigliata per settings.json, che imposta Ruff come formattatore predefinito e definisce regole esplicite (utili se non si usa pyproject.toml):

{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
}
},
// Configurazione opzionale: Definizione regole direttamente in VS Code
// (Utile se non si desidera usare pyproject.toml)
"ruff.lineLength": 88,
"ruff.lint.select": [
"E", // pycodestyle errors
"W", // pycodestyle warnings
"F", // pyflakes
"I", // isort
"B", // flake8-bugbear
"SIM", // flake8-simplify
"PL", // flake8-pie
"D", // pydocstyle
"UP" // pyupgrade
],
"python.analysis.typeCheckingMode": "standard",
"python.analysis.autoImportCompletions": true,
"python.analysis.importFormat": "absolute",
"python.analysis.autoIndent": true,
"python.analysis.enableTroubleshootMissingImports": true,
"python.analysis.indexing": false,
"python.languageServer": "Default",
}

9.3 Configurazione B: Workflow Classico (Black, Pylint, isort)

Sezione intitolata “9.3 Configurazione B: Workflow Classico (Black, Pylint, isort)”

Se si lavora su progetti legacy o si necessita dell’analisi statica molto profonda (inferenza di tipi complessa) che Pylint offre ancora in esclusiva.

È necessario installare tutti i tool separatamente.

Finestra del terminale
pip install black pylint isort

Microsoft ha deprecato la configurazione monolitica di Python in favore di estensioni separate. È necessario installare dal Marketplace:

  1. Black Formatter (ms-python.black-formatter)

  2. Pylint (ms-python.pylint)

  3. isort (ms-python.isort)

  4. Pylance (ms-python.vscode-pylance) - Fondamentale per l’IntelliSense e il controllo tipi statico di base.

Configurazione .vscode/settings.json:

{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
// Configurazione specifica Pylint
"pylint.args": [
"--max-line-length=88",
"--disable=C0111" // Disabilita check docstrings mancanti
],
// Configurazione isort per compatibilitĂ  con Black
"isort.args": ["--profile", "black"]
}

Per evitare che Black e isort entrino in conflitto, è necessario configurare isort per riconoscere il profilo Black.

[tool.black]
line-length = 88
[tool.isort]
profile = "black"
line_length = 88

Accanto al linting e alla formattazione, lo sviluppo Python professionale richiede un controllo statico dei tipi (static type checking) capace di intercettare errori semantici prima dell’esecuzione: accessi a membri inesistenti, ritorni incompatibili, usi impropri di Optional, importazioni di simboli non coerenti con gli stub, mismatch tra collezioni e type hints. In questo contesto, ty rappresenta una delle proposte piu’ moderne dell’ecosistema: e’ un type checker e language server scritto in Rust, sviluppato da Astral, con un’attenzione particolare a prestazioni, analisi incrementale e integrazione editoriale.

Dal punto di vista architetturale, ty non sostituisce Ruff: i due strumenti operano su piani diversi e complementari.

  • Ruff: linting, stile, formattazione, ordinamento import, classi di bug note.

  • ty: analisi statica dei tipi, coerenza semantica tra annotazioni, inferenza, controllo delle importazioni e diagnostica di tipo.

La combinazione ruff + ty costituisce quindi una toolchain moderna interamente basata su strumenti ad alte prestazioni scritti in Rust.

Un type checker statico non “esegue” il programma: analizza il codice e verifica che le operazioni siano coerenti con i tipi dichiarati o inferiti. Si consideri il seguente esempio minimale:

from typing import Optional
def upper_name(name: Optional[str]) -> str:
return name.upper()

Il codice puo’ sembrare innocuo, ma il parametro name puo’ assumere anche valore None; dunque l’invocazione name.upper() non e’ garantita. Un type checker come ty segnala il problema prima ancora che il ramo difettoso venga eseguito a runtime.

Un esempio corretto e’ il seguente:

from typing import Optional
def upper_name(name: Optional[str]) -> str:
if name is None:
return ""
return name.upper()

Gli scenari in cui ty risulta particolarmente utile sono:

  • basi di codice di media e grande dimensione con molti moduli e import incrociati;

  • progetti con API interne tipizzate (dataclass, TypedDict, Protocol, generics);

  • backend FastAPI o librerie riutilizzabili, ove la stabilita’ delle interfacce pubbliche e’ fondamentale;

  • refactoring estesi, nei quali il type checking previene regressioni silenziose.

2. Modalita’ di installazione: nessun setup, globale, locale al progetto

Sezione intitolata “2. Modalita’ di installazione: nessun setup, globale, locale al progetto”

ty puo’ essere adottato con diversi livelli di persistenza operativa.

Il metodo piu’ rapido e’ usare uvx, che esegue ty in un ambiente effimero ma cache-izzato:

Finestra del terminale
uvx ty check
uvx ty check src tests
uvx ty check --watch

Questa modalita’ e’ ideale per:

  • provare il tool su un repository esistente senza modificare le dipendenze;

  • eseguire verifiche una tantum in aula o in CI sperimentali;

  • valutare rapidamente il livello di rumore diagnostico prima di adottarlo stabilmente.

Se si desidera avere il comando disponibile ovunque nel PATH, si puo’ installare ty come tool globale.

Con uv:

Finestra del terminale
uv tool install ty@latest
ty version

Aggiornamento:

Finestra del terminale
uv tool upgrade ty

Con pipx:

Finestra del terminale
pipx install ty
ty version

Aggiornamento:

Finestra del terminale
pipx upgrade ty

Con pip nell’ambiente Python corrente:

Finestra del terminale
pip install ty
ty version

L’installazione via pip e’ appropriata solo se si accetta di legare ty allo specifico interprete corrente; per uso sistemico risultano generalmente preferibili uv tool install o pipx.

C. Installazione come dipendenza di sviluppo del singolo progetto
Sezione intitolata “C. Installazione come dipendenza di sviluppo del singolo progetto”

Questo e’ il modello piu’ rigoroso per team e repository versionati, poiche’ vincola la versione dello strumento al progetto stesso.

Con uv:

Finestra del terminale
uv add --dev ty
uv run ty check

Aggiornamento controllato della dipendenza:

Finestra del terminale
uv lock --upgrade-package ty
uv sync

Con pip dentro il .venv del progetto:

Finestra del terminale
pip install ty
ty check

In termini metodologici, la regola puo’ essere formulata cosi’:

  • usare uvx ty check per esperimenti o esecuzioni spot;

  • usare uv tool install ty o pipx install ty per un tool personale sempre disponibile;

  • usare uv add --dev ty per garantire coerenza tra tutti i collaboratori del progetto.

Una caratteristica importante di ty e’ la capacita’ di funzionare bene anche in progetti senza configurazione esplicita. In assenza di setup avanzato:

  • cerca l’ambiente virtuale attivo tramite VIRTUAL_ENV;

  • se non trova un ambiente attivo, prova a individuare una directory .venv nella root del progetto;

  • in ultima istanza usa un interprete python3 o python disponibile nel PATH.

Pertanto, in un repository Python semplice, spesso e’ sufficiente:

Finestra del terminale
ty check

Oppure, se si vuole limitare l’analisi a percorsi specifici:

Finestra del terminale
ty check src
ty check src tests
ty check app/main.py

Per mantenere il controllo continuamente attivo durante lo sviluppo:

Finestra del terminale
ty check --watch

Esempio di progetto minimale senza configurazione dedicata:

progetto_minimale/
├── .venv/
├── src/
│ └── main.py
└── pyproject.toml

Se src/main.py contiene annotazioni corrette e il .venv e’ attivo o rilevabile, ty puo’ operare immediatamente senza alcun file ty.toml.

4. Progetto moderno con pyproject.toml: configurazione consigliata

Sezione intitolata “4. Progetto moderno con pyproject.toml: configurazione consigliata”

Quando il progetto usa gia’ pyproject.toml, la configurazione piu’ naturale consiste nell’aggiungere la sezione [tool.ty]. Questo approccio centralizza in un unico file sia i metadati del progetto sia la configurazione del type checker.

Esempio realistico per progetto con layout src/:

[project]
name = "inventory-service"
version = "0.1.0"
requires-python = ">=3.12"
[tool.ty.src]
include = ["src", "tests"]
exclude = ["tests/fixtures/**", "generated/**"]
[tool.ty.environment]
python-version = "3.12"
root = ["./src"]
[tool.ty.rules]
possibly-unresolved-reference = "warn"
[tool.ty.terminal]
output-format = "full"
error-on-warning = false
[[tool.ty.overrides]]
include = ["tests/**", "**/test_*.py"]
[tool.ty.overrides.rules]
possibly-unresolved-reference = "ignore"

Questa configurazione esprime alcune best practice importanti:

  • project.requires-python aiuta ty a inferire correttamente la versione target del linguaggio;

  • [tool.ty.src] delimita esplicitamente il perimetro dell’analisi;

  • [tool.ty.environment] e’ utile per specificare versione Python, root del progetto e, se necessario, interprete o ambiente non standard;

  • [tool.ty.rules] permette di graduare severita’ diverse (ignore, warn, error);

  • [[tool.ty.overrides]] consente di allentare o irrigidire il controllo su aree specifiche, ad esempio test, file generati o directory sperimentali.

Esecuzione tipica in questo scenario:

Finestra del terminale
uv run ty check
uv run ty check --watch

5. Progetto senza pyproject.toml o con configurazione separata: ty.toml

Sezione intitolata “5. Progetto senza pyproject.toml o con configurazione separata: ty.toml”

Non tutti i repository Python adottano una configurazione centralizzata in pyproject.toml. Nei progetti legacy, negli script didattici o nei repository multi-tool puo’ essere preferibile un file dedicato ty.toml.

In questo caso, la struttura della configurazione e’ identica, ma senza il prefisso [tool.ty].

Esempio:

[src]
include = ["src", "scripts"]
exclude = ["build/**", "legacy/**"]
[environment]
python = "./.venv"
python-version = "3.11"
root = ["./src", "./scripts"]
[rules]
possibly-unresolved-reference = "warn"
[analysis]
allowed-unresolved-imports = ["vendor.**"]
[terminal]
output-format = "concise"

Questa variante e’ appropriata quando:

  • il progetto non usa pyproject.toml;

  • si vuole mantenere il type checker separato dalla configurazione packaging/build;

  • si desidera una migrazione graduale, introducendo ty senza riorganizzare il repository.

Va inoltre ricordato che, se in una stessa directory coesistono sia pyproject.toml sia ty.toml, ty.toml ha precedenza per la configurazione di ty.

inventory-service/
├── .venv/
├── pyproject.toml
├── src/
│ └── inventory_service/
│ ├── __init__.py
│ └── api.py
└── tests/
└── test_api.py

Comandi consigliati:

Finestra del terminale
uv add --dev ty
uv run ty check

Configurazione consigliata: root = ["./src"], inclusione di src e tests, override meno severi per test o fixture.

script-utilities/
├── .venv/
├── scripts/
│ ├── cleanup.py
│ └── report.py
└── ty.toml

Comandi consigliati:

Finestra del terminale
ty check scripts
ty check --watch scripts

Configurazione tipica: include = ["scripts"], python = "./.venv", output conciso.

In codebase storiche e’ frequente incontrare librerie prive di type hints affidabili o moduli interni non risolti perfettamente. In tali casi, si puo’ adottare una strategia graduale:

[tool.ty.analysis]
allowed-unresolved-imports = ["legacy_vendor.**"]
replace-imports-with-any = ["pandas.**"]
[tool.ty.rules]
possibly-unresolved-reference = "warn"

Questo approccio riduce il rumore iniziale, consentendo di introdurre il type checking in modo incrementale invece di bloccare immediatamente tutta la pipeline.

7. Esecuzione esplicita con interprete o ambiente non standard

Sezione intitolata “7. Esecuzione esplicita con interprete o ambiente non standard”

Se ty non riesce a individuare correttamente l’ambiente, e’ possibile specificarlo in modo esplicito.

Da CLI:

Finestra del terminale
ty check --python .venv
ty check --python .venv/bin/python
ty check --python .\.venv

Oppure via configurazione:

[tool.ty.environment]
python = "./custom-venv/.venv"
python-version = "3.12"

Questo e’ particolarmente utile quando:

  • il virtual environment si trova in una directory non convenzionale;

  • si lavora con piu’ ambienti Python paralleli;

  • si vuole rendere deterministica la risoluzione delle dipendenze in CI/CD.

L’integrazione editoriale di ty e’ uno dei punti piu’ interessanti dello strumento. Astral mantiene una estensione ufficiale per VS Code: astral-sh.ty.

L’estensione offre:

  • diagnostica di type checking in tempo reale;

  • linguaggio server con navigazione del codice, completamenti, hover, auto-import e inlay hints;

  • integrazione diretta con la configurazione di ty e con l’ambiente Python del workspace.

Questa modalita’ e’ coerente con una toolchain completamente centrata su Astral (ruff + ty + uv). L’estensione ty imposta di default python.languageServer a "None" per evitare di eseguire contemporaneamente due language server Python.

Configurazione consigliata di .vscode/settings.json:

{
"python.languageServer": "None",
"ty.diagnosticMode": "workspace",
"ty.inlayHints.variableTypes": true,
"ty.inlayHints.callArgumentNames": true,
"ty.completions.autoImport": true,
"ty.importStrategy": "fromEnvironment"
}

Questa configurazione e’ adatta quando si vuole che ty gestisca sia type checking sia servizi LSP principali.

Modalita’ B: usare ty solo per type checking e Pylance per IntelliSense
Sezione intitolata “Modalita’ B: usare ty solo per type checking e Pylance per IntelliSense”

In alcuni team puo’ essere preferibile mantenere Pylance per completamento, navigazione e funzionalita’ consolidate Microsoft, delegando a ty la sola diagnostica di tipo.

Configurazione consigliata:

{
"python.languageServer": "Pylance",
"ty.disableLanguageServices": true,
"ty.diagnosticMode": "workspace",
"ty.showSyntaxErrors": false
}

Questa modalita’ e’ utile quando:

  • il team usa gia’ Pylance e non vuole modificare radicalmente l’ergonomia editoriale;

  • si desidera confrontare gradualmente la qualita’ diagnostica di ty rispetto a Pyright/Pylance;

  • si vuole evitare la sovrapposizione di hover, completamenti e definizioni provenienti da due server distinti.

Se il workspace usa una installazione locale o un path non standard, l’estensione consente di indicare esplicitamente l’eseguibile:

{
"ty.path": ["${workspaceFolder}/.venv/Scripts/ty.exe"],
"ty.diagnosticMode": "workspace"
}

Oppure, in ambiente POSIX:

{
"ty.path": ["${workspaceFolder}/.venv/bin/ty"]
}

In alternativa, si puo’ indicare un interprete Python da cui l’estensione tentera’ di ricavare ty:

{
"ty.interpreter": ["${workspaceFolder}/.venv/bin/python"]
}

Alla luce dei test pratici piu’ comuni in VS Code, e’ utile distinguere tre profili operativi gia’ pronti, ciascuno con obiettivi diversi. I seguenti blocchi sono concepiti per essere copiati nella configurazione utente o di workspace, fermo restando che l’impostazione python.defaultInterpreterPath e’ da intendersi come fallback iniziale e non come selezione permanente dell’interprete del progetto.

Se si desidera un interprete globale iniziale per i workspace nuovi, si puo’ usare:

"python.defaultInterpreterPath": "c:\\Program Files\\Python313\\python.exe"

Se invece si lavora sistematicamente con ambienti virtuali locali al repository, e’ spesso preferibile omettere questa impostazione nelle user settings e selezionare l’interprete del progetto tramite il comando Python: Select Interpreter.

Questo e’ il profilo piu’ conservativo e, nella pratica, spesso quello che fornisce l’hover piu’ ricco e dettagliato su classi, funzioni e simboli della libreria standard.

"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
}
},
"ruff.lineLength": 88,
"ruff.lint.select": [
"E",
"W",
"F",
"I",
"B",
"SIM",
"PL",
"D",
"UP"
],
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "standard",
"python.analysis.autoImportCompletions": true,
"python.analysis.importFormat": "absolute",
"python.analysis.autoIndent": true,
"python.analysis.enableTroubleshootMissingImports": true,
"python.analysis.indexing": false

Cosa viene attivato in questo setup:

  • Ruff: formattazione, linting, fix automatici e organizzazione import;

  • Pylance: hover, completamento, go to definition, IntelliSense e type checking;

  • ty: non coinvolto nel workflow editoriale.

Quando sceglierlo:

  • se si desidera la massima qualita’ dell’esperienza editoriale in VS Code;

  • se si preferiscono hover piu’ ricchi e documentativi;

  • se non e’ necessario introdurre ty direttamente nell’editor.

Questo profilo disattiva Pylance come language server e affida a ty sia il type checking sia i servizi LSP principali.

"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
}
},
"ruff.lineLength": 88,
"ruff.lint.select": [
"E",
"W",
"F",
"I",
"B",
"SIM",
"PL",
"D",
"UP"
],
"python.languageServer": "None",
"ty.disableLanguageServices": false,
"ty.diagnosticMode": "workspace",
"ty.inlayHints.variableTypes": true,
"ty.inlayHints.callArgumentNames": true,
"ty.completions.autoImport": true

Cosa viene attivato in questo setup:

  • Ruff: formattazione, linting, fix automatici e organizzazione import;

  • ty: type checking, hover, completamento, go to definition, inlay hints e auto-import;

  • Pylance: disattivato come language server.

Quando sceglierlo:

  • se si vuole una toolchain coerente con l’ecosistema Astral (uv + ruff + ty);

  • se si accetta un hover talvolta piu’ sintetico rispetto a Pylance;

  • se si desidera che il controllo statico e i servizi di linguaggio provengano dallo stesso tool.

Questo profilo ibrido mantiene Pylance per l’ergonomia editoriale e usa ty solo per la diagnostica di tipo.

"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
}
},
"ruff.lineLength": 88,
"ruff.lint.select": [
"E",
"W",
"F",
"I",
"B",
"SIM",
"PL",
"D",
"UP"
],
"python.analysis.typeCheckingMode": "off",
"python.analysis.autoImportCompletions": true,
"python.analysis.importFormat": "absolute",
"python.analysis.autoIndent": true,
"python.analysis.enableTroubleshootMissingImports": true,
"python.analysis.indexing": false,
"python.languageServer": "Pylance",
"ty.disableLanguageServices": true,
"ty.diagnosticMode": "workspace"

Cosa viene attivato in questo setup:

  • Ruff: formattazione, linting, fix automatici e organizzazione import;

  • Pylance: hover, completamento, go to definition e IntelliSense;

  • ty: type checking diagnostico nel workspace, senza hover o altri language services;

  • Pylance type checking: disattivato, per evitare sovrapposizioni con ty.

Quando sceglierlo:

  • se si desidera mantenere l’hover e l’IntelliSense di Pylance;

  • se si vuole sperimentare ty come solo type checker;

  • se si desidera una migrazione graduale verso toolchain piu’ moderne.

Osservazione pratica importante:

  • in molti ambienti reali, il primo setup (Ruff + Pylance) continua a offrire l’hover piu’ completo e ricco;

  • il secondo setup (Ruff + ty) privilegia la coerenza della toolchain Astral, ma puo’ mostrare hover piu’ essenziali;

  • il terzo setup (Ruff + Pylance + ty) e’ teoricamente il piu’ bilanciato, ma va verificato empiricamente nel proprio ambiente VS Code, poiche’ l’interazione tra estensioni puo’ variare.

Finestra del terminale
uvx ty check
uvx ty check --watch

Ideale per audit iniziale di un repository esistente.

Finestra del terminale
uv add --dev ruff ty
uv run ruff check .
uv run ty check

Questo e’ probabilmente il workflow piu’ pulito per un nuovo progetto Python moderno.

Finestra del terminale
uv tool install ty@latest
ty check ~/progetti/mio-script

Appropriato per chi lavora spesso su repository eterogenei e vuole avere ty sempre disponibile senza entrare ogni volta in un venv.

Si raccomanda ty quando:

  • si desidera introdurre type checking statico in modo moderno e veloce;

  • si lavora gia’ con uv e ruff e si vuole mantenere coerenza di ecosistema;

  • si vuole usare un language server capace di type checking incrementale ad alte prestazioni;

  • si preferisce una configurazione graduale, con regole modulabili tra warn, error e ignore.

Si raccomanda invece di procedere con cautela nei seguenti casi:

  • codebase molto legacy con annotazioni quasi assenti, ove potrebbe essere necessario un onboarding graduale;

  • repository in cui il team dipende fortemente da un setup editoriale gia’ costruito attorno a Pylance o mypy;

  • pipeline esistenti che richiedono una migrazione pianificata delle convenzioni di type checking.

In sintesi, ty non va interpretato come semplice “linter dei tipi”, ma come un componente strutturale della qualita’ del software Python moderno: puo’ essere usato senza setup, installato globalmente o versionato nel progetto, e si integra bene sia in workflow minimali sia in architetture professionali basate su uv, ruff e VS Code.

Nel panorama attuale di VS Code, Pylance e ty possono essere visti come due strategie differenti per portare intelligence editoriale e controllo statico nel workflow Python.

  • Pylance: eccelle nell’ecosistema Microsoft, offre IntelliSense maturo ed e’ basato su Pyright.

  • ty: unisce type checking e language server in un tool unico, moderno e molto veloce, integrato nativamente con l’ecosistema Astral.

  • Ruff/Pylint: continuano a coprire il dominio del linting, dello stile e di alcune classi di bug non prettamente tipizzate.

Le combinazioni operative piu’ sensate oggi sono:

  • Ruff + Pylance: workflow molto diffuso, conservativo e ben supportato.

  • Ruff + ty: workflow moderno, coerente con uv, ad alte prestazioni.

  • Ruff + Pylance + ty (solo diagnostica): soluzione ibrida per migrazioni graduali.

10. Tassonomia dei Casi d’Uso e Architetture di Riferimento

Sezione intitolata “10. Tassonomia dei Casi d’Uso e Architetture di Riferimento”

L’architettura del file system e la gestione delle dipendenze non sono monolitiche; esse variano sensibilmente in funzione della tipologia di applicazione. Di seguito si delineano quattro paradigmi architetturali comuni, evidenziando le specificità strutturali e i requisiti di configurazione.

10.1 Progetti Backend ad Alte Prestazioni (Microservizi/FastAPI)

Sezione intitolata “10.1 Progetti Backend ad Alte Prestazioni (Microservizi/FastAPI)”

In contesti ove si predilige l’uso di framework asincroni moderni come FastAPI, la struttura deve favorire la containerizzazione (Docker) e la separazione dei contesti di esecuzione.

  • PeculiaritĂ : Uso intensivo di uvicorn come server ASGI; necessitĂ  rigorosa di file di lock (uv.lock o requirements.lock) per garantire la stabilitĂ  nei container di produzione.

  • Struttura Consigliata:

    my-microservice/
    ├── app/
    │ ├── __init__.py
    │ ├── main.py # Entry point dell'applicazione (FastAPI instance)
    │ ├── routers/ # Separazione delle rotte API
    │ └── core/ # Configurazioni e modelli Pydantic
    ├── tests/ # Suite di test (pytest)
    ├── Dockerfile # Istruzioni di containerizzazione
    ├── pyproject.toml # Definizione dipendenze
    └── uv.lock # Snapshot deterministico delle versioni

Per strumenti destinati all’uso da terminale (sviluppati con librerie quali Click, Typer o Argparse), l’obiettivo primario è la distribuibilità. Tali progetti sono spesso progettati per essere installati via pipx.

  • PeculiaritĂ : Definizione degli entry points nel file pyproject.toml (sezione [project.scripts]) per rendere eseguibile il comando; il codice sorgente è tipicamente incapsulato in una directory con il nome del tool.

  • Configurazione Entry Point (pyproject.toml):

    [project.scripts]
    my-tool = "my_cli_package.main:app"
  • Struttura Consigliata:

    my-cli-tool/
    ├── my_cli_package/
    │ ├── __init__.py
    │ ├── main.py # Logica principale e parsing argomenti
    │ └── utils.py
    ├── pyproject.toml # Contiene la definizione dello script eseguibile
    └── README.md

I progetti che impiegano framework grafici pesanti (come PyQt6, PySide6 o Tkinter) presentano sfide uniche riguardanti la gestione degli asset statici e la compilazione in eseguibili standalone.

  • PeculiaritĂ : Gestione di file non-codice (icone, file .ui, immagini) che non devono essere tracciati se generati automaticamente; dipendenze di sistema complesse; uso frequente di strumenti di “freezing” come PyInstaller.

  • Nota sul .gitignore: È cruciale ignorare le cartelle di build generate dai compilatori (build/, dist/) e i file spec temporanei.

  • Struttura Consigliata:

    my-gui-app/
    ├── assets/ # Icone, immagini, fogli di stile (non codice)
    ├── src/
    │ ├── ui/ # Layout grafici
    │ ├── controllers/ # Logica applicativa
    │ └── app.py # Entry point GUI
    ├── build_scripts/ # File .spec per PyInstaller
    ├── requirements.txt
    └── main.py # Wrapper di avvio

I progetti Django seguono una convenzione strutturale rigida (“Convention over Configuration”) che differisce dai microservizi.

  • PeculiaritĂ : Presenza dello script di gestione manage.py nella root; necessitĂ  di separare le configurazioni (settings.py) per ambienti di sviluppo e produzione.

  • Best Practice: Evitare di avere un unico settings.py. Suddividere la configurazione in un modulo dedicato.

  • Struttura Consigliata:

    my-django-project/
    ├── config/ # (Rinomina della cartella di progetto default)
    │ ├── __init__.py
    │ ├── settings/ # Modulo impostazioni
    │ │ ├── __init__.py
    │ │ ├── base.py # Configurazioni comuni
    │ │ ├── local.py # Configurazione Dev (debug=True)
    │ │ └── production.py # Configurazione Prod (debug=False)
    │ ├── urls.py
    │ └── wsgi.py
    ├── apps/ # Contenitore per le app Django riutilizzabili
    │ ├── utenti/
    │ └── catalogo/
    ├── manage.py # Script di gestione (non modificare)
    ├── pyproject.toml
    └── .env # Variabili d'ambiente (IGNORATO da Git)

10.5 Sistemi di Intelligenza Artificiale Generativa (RAG con LangChain e FastAPI)

Sezione intitolata “10.5 Sistemi di Intelligenza Artificiale Generativa (RAG con LangChain e FastAPI)”

I progetti di IA moderna, in particolare quelli basati su architetture RAG (Retrieval-Augmented Generation), presentano una complessitĂ  notevole in termini di gestione delle dipendenze (spesso molto pesanti, come torch o driver CUDA) e di segregazione delle responsabilitĂ  tra il livello di servizio (API) e il livello cognitivo (Chains/LLM).

  • PeculiaritĂ : NecessitĂ  di gestire chiavi API sensibili (OpenAI, HuggingFace) tramite file .env rigorosamente esclusi da Git; coesistenza di codice di produzione e notebook di prototipazione sperimentale; gestione di database vettoriali locali (ChromaDB, FAISS) che richiedono directory di persistenza da ignorare nel versionamento.

  • Architettura Consigliata: Si raccomanda di disaccoppiare la logica delle “Catene” (Chains) dall’interfaccia REST.

  • Struttura Consigliata:

    rag-service-app/
    ├── app/
    │ ├── api/ # Layer di interfaccia REST (FastAPI)
    │ │ ├── endpoints/ # Routes (es. /chat, /ingest)
    │ │ └── schemas/ # Modelli Pydantic per validazione I/O
    │ ├── core/ # Configurazioni globali
    │ ├── chains/ # Logica LangChain (Prompt Templates, LCEL)
    │ └── vectorstore/ # Astrazione per accesso al Vector DB (Chroma/Qdrant)
    ├── data/ # Documenti grezzi (PDF, TXT) per ingestion (IGNORARE se sensibili)
    ├── notebooks/ # Jupyter Notebooks per sperimentazione (EDA, Prompt Engineering)
    ├── .env # Chiavi API (CRUCIALE: Ignorare in Git)
    ├── pyproject.toml # Dipendenze (langchain, fastapi, openai, chromadb)
    └── uv.lock

11. Sviluppo in Container: Docker, Docker Compose e VS Code DevContainers

Sezione intitolata “11. Sviluppo in Container: Docker, Docker Compose e VS Code DevContainers”

Mentre gli ambienti virtuali (venv, conda) isolano le dipendenze Python, i container Docker estendono questo isolamento all’intero sistema operativo (librerie di sistema, database, configurazioni di rete). L’utilizzo dei DevContainers in VS Code rappresenta lo standard industriale per garantire che l’intero team di sviluppo operi in condizioni identiche, eliminando il problema del “funziona sulla mia macchina”.

Un DevContainer (Development Container) è un contenitore Docker in cui VS Code “inietta” il proprio server backend. Quando si apre un progetto in un DevContainer:

  1. VS Code legge la configurazione .devcontainer/devcontainer.json.

  2. Costruisce o scarica l’immagine Docker specificata.

  3. Avvia il container e vi monta il codice sorgente (volume mapping).

  4. Installa automaticamente le estensioni di VS Code (es. Python, Ruff, Pylance) dentro il container.

PerchĂŠ adottarlo:

  • Onboarding immediato: Un nuovo sviluppatore clona il repo, preme “Reopen in Container” e ha tutto pronto (Python, DB, Redis, Tools) senza installare nulla sul proprio PC.

  • Isolamento Totale: È possibile lavorare su un progetto Python 3.7 legacy e uno 3.12 moderno simultaneamente senza conflitti di PATH o librerie di sistema.

Per utilizzare questa tecnologia è necessario:

  1. Docker Desktop (Windows/Mac) o Docker Engine (Linux). Su Mac è possibile usare alternative leggere come OrbStack.

  2. Visual Studio Code.

  3. Estensione VS Code: Dev Containers (id: ms-vscode-remote.remote-containers).

Nella root del progetto, creare la cartella .devcontainer e il file devcontainer.json.

Esempio di devcontainer.json per Python Moderno:

{
"name": "Python 3.12 Project",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"features": {
// Aggiunge la CLI di Docker dentro il container (Docker-in-Docker)
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
// Aggiunge utility comuni (git, zsh, ecc.)
"ghcr.io/devcontainers/features/common-utils:2": {
"configureZshAsDefaultShell": true
}
},
// Configurazione specifica dell'editor all'interno del container
"customizations": {
"vscode": {
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.formatting.provider": "none",
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
},
// Estensioni da installare automaticamente nel container
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"njpwerner.autodocstring"
]
}
},
// Comando post-creazione per installare le dipendenze
// Nota: In un container, spesso si usa l'interprete di sistema (root) o un venv utente.
"postCreateCommand": "pip install --user -r requirements.txt",
// Utente remoto (vscode è l'utente predefinito nelle immagini Microsoft)
"remoteUser": "vscode"
}

Per applicazioni reali che richiedono servizi accessori (Database Postgres, Cache Redis), un singolo container non basta. Si usa Docker Compose orchestrato dal DevContainer.

Struttura File:

.devcontainer/
├── devcontainer.json
└── docker-compose.yml

1. devcontainer.json (Versione Compose):

{
"name": "Full Stack Python Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app", // Indica quale servizio del compose è l'ambiente di sviluppo
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"extensions": ["ms-python.python", "charliermarsh.ruff"]
}
},
"postCreateCommand": "pip install -r requirements.txt",
"remoteUser": "vscode"
}

2. docker-compose.yml (Definizione Servizi):

version: '3.8'
services:
app:
# Costruisce l'immagine dall'esterno o usa un'immagine base
image: mcr.microsoft.com/devcontainers/python:3.11
volumes:
# Monta la cartella corrente (il progetto) in /workspace
- ..:/workspace:cached
# Mantiene il container attivo all'infinito
command: sleep infinity
# Rete condivisa con il database
depends_on:
- db
- redis
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydatabase
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:alpine
volumes:
postgres-data:
  1. Apertura: Aprire VS Code nella cartella del progetto.

  2. Rilevamento: VS Code rileverà la cartella .devcontainer e mostrerà una notifica in basso a destra: “Folder contains a Dev Container configuration file. Reopen to develop in a container.”

  3. Build: Cliccando su “Reopen in Container”, VS Code avvierà la costruzione dell’immagine. La prima volta potrebbe richiedere alcuni minuti.

  4. Sviluppo: Una volta caricato, il terminale di VS Code sarĂ  una shell dentro il sistema Linux del container.

    • python --version restituirĂ  la versione del container.

    • Il codice modificato è sincronizzato in tempo reale con il file system locale.

  5. Debug: Premendo F5, l’applicazione verrà eseguita nel container, ma il debugger grafico di VS Code funzionerà normalmente.

Gestione del ciclo di vita:

  • Per tornare all’ambiente locale: F1 -> Dev Containers: Reopen Folder Locally.

  • Per ricostruire il container (se si cambia il Dockerfile): F1 -> Dev Containers: Rebuild Container.

Per progetti a finalità didattica o al fine di agevolare l’inserimento (onboarding) di nuovi collaboratori con minore esperienza tecnica, risulta eccellente prassi l’inclusione di uno script di “bootstrapping”. Tale script ha la funzione di astrarre la complessità dei comandi sottostanti e configurare l’ambiente in modalità automatica e resiliente.

Il seguente script è progettato secondo i principi della defensive programming: gestisce le eccezioni, rileva dinamicamente il sistema operativo in uso, verifica i prerequisiti e fornisce un feedback visivo chiaro all’utente. Non richiede dipendenze esterne, avvalendosi esclusivamente della libreria standard di Python per garantire la massima portabilità.

import os
import subprocess
import sys
import platform
def print_step(message):
"""Visualizza un messaggio di stato formattato per migliorare la leggibilitĂ ."""
print(f"\n{'='*40}")
print(f"[*] {message}")
print(f"{'='*40}")
def get_pip_command(venv_path):
"""
Determina il percorso assoluto corretto dell'eseguibile pip all'interno del venv,
gestendo le differenze strutturali tra sistemi Windows (Scripts) e POSIX (bin).
"""
if os.name == "nt": # Rilevamento ambiente Windows
return os.path.join(venv_path, "Scripts", "pip.exe")
return os.path.join(venv_path, "bin", "pip")
def setup():
venv_name = ".venv"
# 1. Verifica preliminare della versione di Python in uso
print_step(f"Verifica Ambiente: Python {platform.python_version()} rilevato su {platform.system()}")
# 2. Creazione dell'Ambiente Virtuale (se non presente)
if not os.path.exists(venv_name):
print_step(f"Inizializzazione ambiente virtuale nella directory '{venv_name}'...")
try:
# Esecuzione del modulo venv come sottoprocesso
subprocess.check_call([sys.executable, "-m", "venv", venv_name])
print("Ambiente virtuale creato con successo.")
except subprocess.CalledProcessError:
print("[ERRORE FATALE] Impossibile creare il venv. Verificare i permessi o l'installazione di Python.")
sys.exit(1)
else:
print(f"Ambiente '{venv_name}' preesistente rilevato. Procedura di creazione saltata.")
# 3. Aggiornamento pip e installazione dipendenze
pip_cmd = get_pip_command(venv_name)
# Verifica integritĂ : controllo esistenza del binario pip
if not os.path.exists(pip_cmd):
print(f"[ERRORE FATALE] Pip non rilevato nel percorso atteso: {pip_cmd}.")
print("Il venv potrebbe essere corrotto o incompleto. Si consiglia di rimuovere la cartella .venv e riprovare.")
sys.exit(1)
print_step("Aggiornamento del gestore pacchetti e installazione dipendenze...")
try:
# Aggiornamento preventivo di pip per garantire compatibilitĂ  con le ultime wheel
subprocess.check_call([pip_cmd, "install", "--upgrade", "pip"])
# Installazione dei requisiti, ove presenti
if os.path.exists("requirements.txt"):
print("File requirements.txt rilevato. Inizio installazione...")
subprocess.check_call([pip_cmd, "install", "-r", "requirements.txt"])
print("\n[SUCCESSO] Tutte le dipendenze sono state installate correttamente nell'ambiente virtuale.")
else:
print("\n[ATTENZIONE] Nessun file 'requirements.txt' rilevato. L'ambiente è attivo ma vuoto.")
except subprocess.CalledProcessError as e:
print(f"\n[ERRORE] Fallimento critico durante l'installazione dei pacchetti: {e}")
print("Verificare la connettivitĂ  di rete o la compatibilitĂ  dei pacchetti nel file requirements.")
sys.exit(1)
# 4. Istruzioni finali all'utente per l'attivazione manuale
print_step("SETUP COMPLETATO")
print("L'ambiente è pronto. Per attivarlo manualmente, utilizzare il comando appropriato:")
if os.name == "nt":
print(f" .\\{venv_name}\\Scripts\\activate")
else:
print(f" source {venv_name}/bin/activate")
if __name__ == "__main__":
setup()

13. Compendio degli Strumenti Citati e Riferimenti Documentali

Sezione intitolata “13. Compendio degli Strumenti Citati e Riferimenti Documentali”

Al fine di offrire un quadro tecnico completo, si riporta di seguito una rassegna sistematica degli strumenti, librerie e framework citati nel presente trattato. Per ciascuna voce è fornita una sintetica descrizione architetturale e il riferimento alla documentazione ufficiale.

  • Poetry: Strumento integrato per la gestione delle dipendenze e il packaging in Python. Utilizza pyproject.toml come standard, gestisce automaticamente i file di lock (poetry.lock) e semplifica la pubblicazione su PyPI.

  • PDM: Gestore di pacchetti moderno che supporta la PEP 582 (pacchetti locali senza virtualenv obbligatorio) e offre un resolver estremamente veloce.

  • Pipenv: Strumento che mira a portare il meglio di tutti i mondi del packaging (bundler, composer, npm, cargo, yarn, etc.) nel mondo Python.

  • Black: Il formattatore di codice Python “senza compromessi”. Riformatta l’intero file in base a regole rigide, garantendo uno stile uniforme (PEP 8 compliant) e riducendo al minimo le discussioni sulle convenzioni di stile durante la code review.

  • Ruff: Un linter e formattatore Python estremamente performante, scritto in Rust. È progettato per essere un sostituto “drop-in” molto piĂš veloce di Flake8, Black, isort e molti altri strumenti, aggregando oltre 500 regole di analisi.

  • Flake8: Strumento classico che combina PyFlakes (controllo errori logici), pycodestyle (controllo stile PEP 8) e mccabe (controllo complessitĂ  ciclomatica).

  • FastAPI: Framework web moderno e ad alte prestazioni per la costruzione di API con Python 3.8+, basato sulle type hints standard. Offre validazione automatica dei dati (tramite Pydantic) e documentazione interattiva (Swagger UI).

  • Django: Framework web di alto livello “batteries-included” che incoraggia uno sviluppo rapido e un design pulito e pragmatico. Gestisce nativamente ORM, autenticazione e amministrazione.

  • Uvicorn: Un’implementazione server web ASGI velocissima, basata su uvloop e httptools. È il server standard per eseguire applicazioni FastAPI.

  • Click: Pacchetto Python per creare interfacce a riga di comando componibili in modo semplice, con il minor codice necessario. È altamente configurabile e supporta nesting arbitrario di comandi.

  • Typer: Libreria per costruire CLI basata sulle type hints di Python (costruita sopra Click). Permette di definire comandi semplicemente scrivendo funzioni con argomenti tipizzati.

  • Argparse: Libreria standard di Python per il parsing degli argomenti da riga di comando. Sebbene piĂš verbosa delle alternative moderne, non richiede dipendenze esterne.

  • LangChain: Framework per lo sviluppo di applicazioni alimentate da modelli linguistici (LLM). Fornisce astrazioni per catene di prompt, agenti, memoria e indici per il recupero di informazioni (RAG).

  • ChromaDB: Database vettoriale open-source progettato per l’IA. Permette di memorizzare embeddings e recuperare documenti semanticamente simili per applicazioni LLM.

  • PyTorch (Torch): Libreria open source di machine learning, utilizzata per applicazioni come la visione artificiale e l’elaborazione del linguaggio naturale.

  • Httpie: Un client HTTP da riga di comando moderno e user-friendly. Offre un’interfaccia semplice per inviare richieste JSON, output colorato e sintassi intuitiva.

  • PyInstaller: Strumento che “congela” (freezes) applicazioni Python in eseguibili stand-alone, funzionanti su Windows, GNU/Linux, Mac OS X e altri, senza richiedere l’installazione dell’interprete Python sulla macchina target.

  • MkDocs: Generatore di siti statici veloce e semplice, orientato alla documentazione di progetto. La documentazione è scritta in Markdown e configurata tramite un singolo file YAML.