Un notebook è una cosa profondamente seducente. Scrivi una riga, premi Shift+Invio, vedi l’output. Ne scrivi un’altra, ne vedi un’altra. Un’ora dopo hai un’analisi funzionante senza aver mai pensato a struttura del file, confini fra funzioni o import. I notebook sono il punto in cui Python si avvicina di più alla fluidità di un foglio di calcolo.
Sono anche il punto in cui Python si avvicina di più a spararti su un piede, perché la stessa proprietà che li rende dipendenza, il kernel che mantiene lo stato fra una cella e l’altra, è la proprietà che marcisce silenziosamente il tuo lavoro.
Questa lezione parla di entrambi i lati. Dove i notebook si guadagnano la loro reputazione, dove falliscono, e il momento in cui portare il codice in un file .py.
Cos’è davvero un notebook
Un notebook Jupyter è un file JSON con estensione .ipynb. Contiene una lista ordinata di celle, ognuna delle quali è codice o markdown. Quando esegui una cella di codice, il testo viene mandato a un kernel, un processo Python che gira in background, che lo valuta e restituisce il risultato. Il risultato, comprensivo di stdout, stderr, valore di ritorno e qualsiasi rappresentazione ricca (grafici, tabelle, HTML), viene memorizzato di nuovo nel JSON.
Tre cose seguono da questo design:
- Il kernel ha stato. Una variabile definita nella cella 3 è visibile nella cella 7.
- Puoi eseguire le celle in qualsiasi ordine. La cella 7 può girare prima della cella 3.
- L’output salvato nel file è l’output di quando hai eseguito la cella l’ultima volta, non necessariamente l’output dell’esecuzione del notebook intero dall’alto verso il basso.
Rileggile. Insieme spiegano quasi ogni storia dell’orrore sui notebook che hai mai sentito.
Dove i notebook brillano
Esplorazione: hai un CSV, vuoi sapere cosa c’è dentro. Lo carichi, .head(), .describe(), plotti un istogramma, fai uno slice per colonna. Il loop di feedback edit-run-see è imbattibile.
import pandas as pd
df = pd.read_csv("sales.csv")
df.head()
Insegnamento: un notebook con testo, codice e output intervallati è un artefatto completo. Il lettore vede sia cosa hai digitato sia cosa è uscito.
Analisi da consegnare a uno stakeholder: rendering in HTML o PDF, mandi il link, fatto. Il destinatario vede codice, risultati e prosa in un unico documento.
Prototipazione ML: training loop che vuoi ispezionare a metà esecuzione, plot intermedi, iperparametri che modifichi a mano. Il fatto che il kernel resti vivo fra le celle significa che non ricarichi il modello ogni volta che cambi un learning rate.
Dove i notebook falliscono
Codice di produzione: i notebook sono quasi impossibili da schedulare, monitorare o testare. Puoi far girare un notebook da cron con papermill, ma nel momento in cui lo fai hai avvolto uno strumento interattivo con stato dentro un’impalcatura di script, e adesso hai due problemi.
Version control: il formato JSON include output delle celle, contatori di esecuzione e metadata. Due persone che fanno girare lo stesso notebook producono file diversi anche se il codice è identico. I diff di Git sono illeggibili. Esistono tool per togliere gli output (nbstripout) ma devi ricordarti di installarli in ogni clone.
Librerie riutilizzabili: una funzione definita nella cella 4 non è importabile da un altro notebook senza copia-incolla. Puoi scrivere %run other.ipynb per eseguire un altro notebook nel kernel corrente, ma adesso il tuo grafo delle dipendenze è invisibile.
Stato nascosto: l’assassino. Definisci df nella cella 1, lo modifichi nella cella 3, poi torni alla cella 2 e la riesegui. La variabile ha ancora le modifiche della cella 3. Sei mesi dopo qualcuno riesegue il notebook dall’alto verso il basso e ottiene numeri diversi. Pensa che siano cambiati i dati. I dati non sono cambiati. È cambiato l’ordine delle celle.
La soluzione per lo stato nascosto è semplice in linea di principio: restart and run all prima di fidarti del risultato, prima di committare, prima di condividere. Falla diventare un riflesso.
I due IDE nel 2026
Jupyter Lab è l’ambiente web originale. Avvii un server (jupyter lab), apri un browser, ottieni un albero file a sinistra e tab di notebook al centro. Resta l’esperienza più pulita per il lavoro puro su notebook, soprattutto con estensioni come jupyterlab-git e l’inspector delle variabili.
VS Code notebook (anche Cursor, Zed e altri fork di VS Code) hanno aperto la porta a una generazione di sviluppatori che già viveva nel proprio editor. Apri direttamente un .ipynb, l’editor lo renderizza in linea, e ottieni l’esperienza IDE completa: type checking, refactoring, terminale integrato, source control. Nel 2026 questa è la scelta dominante per la maggior parte degli sviluppatori con cui lavoro, e la ragione è semplice: sei a una scorciatoia da tastiera dai tuoi file .py nella stessa finestra.
Scegli quello che preferisci. Vanno bene entrambi. Il formato del file notebook è identico, quindi puoi passare dall’uno all’altro.
Igiene del kernel
Dai al kernel il nome dell’ambiente virtuale del progetto. Dal venv attivato del progetto:
pip install ipykernel
python -m ipykernel install --user --name myproject --display-name "Python (myproject)"
Adesso Python (myproject) compare nel selettore di kernel. Tre motivi per cui conta:
- Non importerai per sbaglio pacchetti dall’ambiente sbagliato.
- Il notebook registra quale kernel si aspetta, così i collaboratori vedono il nome giusto.
- Puoi avere un kernel per progetto senza inquinare il Python globale.
Quando qualcosa smette di funzionare e non riesci a capire perché, fai restart del kernel. Metà di tutti i bug “strani” dei notebook sono oggetti vecchi in memoria.
Il pattern percent-cell
Il segreto meglio custodito del tooling Python moderno: un normale file .py con marker # %% si comporta come un notebook in VS Code, Cursor, PyCharm e Spyder.
# %% Imports
import pandas as pd
import numpy as np
# %% Load data
df = pd.read_csv("sales.csv")
df.head()
# %% Quick stats
df.describe()
# %% Plot
df["revenue"].hist(bins=50)
Apri questo in VS Code, clicchi “Run Cell” sopra qualsiasi blocco # %% e ottieni una finestra interattiva con output, esattamente come un notebook. Il file in sé è puro Python. I diff funzionano. Gli import funzionano. Lo puoi far girare come script con python file.py. Puoi scrivere test per le funzioni che contiene.
È quello che uso di default adesso. Scrivo file .py con marker di cella, salto nella finestra interattiva quando voglio esplorare, e il file resta abbastanza pulito da committarlo. Ricorro a .ipynb solo quando mi serve output renderizzato perché qualcun altro lo legga.
Tool che vale la pena conoscere
nbdev tratta i notebook come fonte della verità per una libreria. Scrivi implementazione, documentazione e test nello stesso .ipynb, e nbdev genera un pacchetto .py e documentazione HTML. Alcuni team lo adorano. L’ho visto funzionare e l’ho visto diventare una palude impossibile da mantenere. La risposta onesta: se sei già una persona notebook-first, provalo; se non lo sei, non farlo.
papermill parametrizza l’esecuzione di un notebook:
papermill analysis.ipynb output.ipynb -p year 2025 -p region "EU"
Inietta i parametri in una cella taggata e fa girare il notebook intero in modalità headless. Utile per report in batch.
jupyter nbconvert trasforma un notebook in HTML, PDF o uno script:
jupyter nbconvert --to html report.ipynb
jupyter nbconvert --to pdf report.ipynb
jupyter nbconvert --to script report.ipynb # ti restituisce un .py
L’export in HTML è il modo giusto di condividere risultati con uno stakeholder non tecnico.
Quando lasciare il notebook
Tengo un piccolo albero decisionale in testa. Quando una qualsiasi di queste è vera, il notebook deve diventare un file .py o un modulo vero e proprio:
- Mi ritrovo a far girare lo stesso notebook su input diversi più di due volte.
- Un collega deve farlo girare e ottenere la stessa risposta in modo affidabile.
- La logica viene copiata in un altro notebook.
- È abbastanza lento da farmi venire voglia di schedularlo.
- Contiene una funzione che voglio usare altrove.
Il percorso di migrazione di solito è: fattorizza la logica pesante in un modulo .py, poi tieni un notebook sottile che lo importa e renderizza l’output.
# analysis.py
def load_clean(path: str) -> pd.DataFrame:
df = pd.read_csv(path)
df = df.dropna(subset=["revenue"])
return df
def summarize(df: pd.DataFrame) -> pd.DataFrame:
return df.groupby("region")["revenue"].agg(["mean", "median", "count"])
# report.ipynb (oppure report.py con marker # %%)
from analysis import load_clean, summarize
df = load_clean("sales.csv")
summarize(df)
Il notebook diventa il livello di presentazione. La logica vive in codice testabile.
Magic che vale la pena conoscere
I magic command di Jupyter sono comandi shortcut prefissati da % (linea) o %% (cella). Una manciata vale la pena tenere nella memoria muscolare:
%timeit expre%%timeitmisurano il tempo di esecuzione con warmup e ripetizione adeguati. Molto meglio che farsi le proprie chiamate atime.time().%load_ext autoreloadseguito da%autoreload 2significa che i tuoi moduli.pyimportati vengono ri-importati automaticamente quando li modifichi. È il singolo settaggio di qualità della vita più grosso per il workflow percent-cell.%env VAR=valueimposta variabili d’ambiente per il kernel.%%writefile name.pyscrive il contenuto di una cella in un file, utile quando hai prototipato una funzione in un notebook e vuoi estrarla.%debugti fa entrare in un debugger post-mortem dopo un’eccezione. Combinato conbreakpoint()della lezione precedente, hai una storia di debugging completa senza lasciare il kernel.
Non devi memorizzare il resto. %lsmagic elenca tutto quello che è disponibile.
Condivisione e riproducibilità
La debolezza più grossa di un notebook come deliverable è che il destinatario ha bisogno dello stesso ambiente per rieseguirlo. Tre pattern aiutano:
- Includi un
requirements.txto unpyproject.tomlaccanto al notebook. Dichiara la tua versione di Python esplicitamente. - Pinna le versioni delle librerie che hanno guidato l’analisi, soprattutto pandas e NumPy, dato che le loro API si evolvono.
- Renderizza in HTML per i consumatori in sola lettura. Non hanno bisogno di far girare niente; devono solo leggere.
Per i collaboratori che rieseguiranno davvero, uv (che abbiamo coperto prima nel corso) rende il setup banalmente veloce. Un README che dice “esegui uv sync && jupyter lab” è sufficiente.
Il verdetto onesto
I notebook sono uno strumento brillante per la prima metà di qualsiasi progetto e lo strumento sbagliato per la seconda metà. La prima metà è “cosa c’è in questi dati, qual è la forma, qual è la storia”. La seconda metà è “fa’ in modo che giri in modo affidabile per sempre”. Cambia strumento quando cambi fase.
Se da anni scrivi notebook resistendo ai file .py, prova il pattern percent-cell per una settimana. Otterrai il feedback interattivo che ami e i diff di cui ha bisogno il tuo te stesso futuro. La prossima lezione mette tutto questo, pandas, NumPy, SciPy e il workflow che abbiamo appena abbozzato, in un singolo progetto end-to-end.