Marimo: Guida Pratica al Notebook Python Reattivo che Sostituisce Jupyter (2026)
Marimo è un notebook Python reattivo salvato come file .py, con esecuzione basata su DAG, elementi UI senza callback e SQL integrato. In questa guida vediamo installazione, migrazione da Jupyter, esempi pratici con pandas e DuckDB, deploy come web app e le novità 2026 di marimo pair.
Marimo è un notebook Python reattivo open source che risolve i due problemi storici di Jupyter (lo hidden state e la riproducibilità) rieseguendo automaticamente le celle a valle quando cambia una variabile, memorizzando ogni notebook come puro file .py versionabile con Git ed eseguibile come script o web app. Nella pratica quotidiana significa che i tuoi risultati corrispondono sempre al codice che li ha prodotti: niente celle eseguite fuori ordine, niente variabili fantasma nel kernel, niente diff illeggibili in pull request.
Marimo è un notebook Python reattivo: modifichi una cella e tutte quelle dipendenti si rieseguono automaticamente tramite un grafo aciclico diretto (DAG).
I file .py puri sono compatibili con Git, esecuzione da riga di comando e strumenti come Ruff, mypy o Claude Code, senza il rumore JSON dei file .ipynb.
La versione 2026 include marimo pair, che permette ad agenti come Claude Code o Codex di guidare una sessione live del notebook con autenticazione sicura.
SQL è nativo: le celle SQL girano su DuckDB, SQLite, Postgres o MySQL e restituiscono DataFrame Polars o pandas usabili nelle celle Python successive.
Elementi UI (slider, dropdown, tabelle) sono variabili Python vincolate al DAG, senza callback e senza il boilerplate tipico di Streamlit.
Uno studio del 2020 su GitHub ha rilevato che oltre il 75% dei notebook Jupyter non eseguono e circa il 96% non riproducono i risultati: Marimo è progettato per rendere strutturalmente impossibili queste condizioni.
Cos'è Marimo e come funziona il modello reattivo
Un notebook reattivo è un ambiente in cui le celle formano un grafo di dipendenze e vengono rieseguite automaticamente quando una loro variabile di ingresso cambia. In pratica, è la stessa logica di un foglio di calcolo applicata al Python. Marimo costruisce staticamente questo grafo analizzando quali variabili globali ogni cella definisce e quali legge, e ne deriva un DAG (Directed Acyclic Graph): quando premi Run su una cella, tutte le celle a valle vengono aggiornate senza intervento manuale, e quando la elimini le sue variabili spariscono dalla memoria del kernel. Addio hidden state di Jupyter.
Il vincolo strutturale che rende questo modello sano è semplice: una variabile globale può essere definita in una sola cella. Se provi a ridefinire df in una seconda cella, Marimo segnala un errore statico prima ancora di eseguire il codice. Onestamente, all'inizio ti sembrerà una scocciatura, ma è ciò che permette al DAG di rimanere coerente. Nella pratica statistica, dove la stessa cella fit() viene rieseguita decine di volte durante il tuning, questa proprietà elimina intere classi di bug di riproducibilità: se il codice gira, gira sempre nello stesso modo.
Jupyter rimane lo standard de facto per l'esplorazione interattiva e ha un vantaggio ecosistemico enorme (kernel multipli, JupyterHub, integrazione con VS Code). Marimo non prova a sostituirlo in ogni scenario: prova a risolvere i problemi specifici che rendono i notebook fragili in produzione. La tabella qui sotto riassume le differenze operative che influenzano ogni giorno chi lavora con i dati.
Caratteristica
Marimo
Jupyter (classic / Lab)
Formato file
.py Python puro
.ipynb JSON con output incorporato
Modello di esecuzione
Reattivo, basato su DAG
Sequenziale manuale, ordine arbitrario
Diff Git
Leggibili, riga per riga
Rumorosi (output binari, metadati)
Elementi UI
Nativi, senza callback
ipywidgets con boilerplate
SQL nativo
Sì, DuckDB integrato
Solo tramite estensioni (jupysql, %%sql)
Deploy come app
marimo run notebook.py
Voilà o Streamlit separati
Esecuzione come script
python notebook.py
Richiede nbconvert o Papermill
Kernel multi-linguaggio
Solo Python
R, Julia, Scala, altri
Integrazione agenti AI (2026)
marimo pair (Claude, Codex)
Estensioni di terze parti
Il punto operativo, in due righe: se il tuo team fa code review di notebook, li mette in CI, li deploya come dashboard o li versiona insieme al resto del codice applicativo, Marimo elimina attriti concreti. Se invece hai una collezione consolidata di notebook R o Julia sotto JupyterHub, Marimo non è la scelta giusta, e va bene così.
Come installare Marimo su Python 3.10+
L'installazione richiede Python 3.10 o superiore. Consiglio sempre un ambiente virtuale isolato (venv, uv o conda) per evitare conflitti con installazioni globali. La distribuzione "recommended" include DuckDB, Polars, Altair, sqlglot e Ruff, quindi non dovrai reinstallare le dipendenze più comuni:
Per creare un nuovo notebook e aprirlo nell'editor browser:
marimo edit analisi.py
Marimo apre http://localhost:2718 con l'editor. Se il file non esiste viene creato vuoto. Per esplorare le funzionalità direttamente dal tutorial integrato:
Primo notebook reattivo: pandas e slider senza callback
Vediamo un esempio concreto che mostra la reattività in azione. Immagina un notebook che carica un dataset di prezzi immobiliari, calcola una statistica riassuntiva filtrata per zona e visualizza la distribuzione. In Jupyter dovresti rieseguire manualmente ogni cella dopo aver cambiato il filtro; in Marimo, uno slider aggiorna tutta la pipeline automaticamente.
# Cella 1: import e caricamento dati
import marimo as mo
import pandas as pd
import numpy as np
df = pd.read_csv(
"housing.csv",
dtype_backend="pyarrow", # backend PyArrow, più veloce su stringhe
)
# Cella 2: elemento UI reattivo
prezzo_max = mo.ui.slider(
start=100_000,
stop=1_000_000,
step=25_000,
value=500_000,
label="Prezzo massimo (€)",
)
prezzo_max
# Cella 3: filtro e statistiche (si rigenera quando lo slider cambia)
filtrato = df[df["price"] <= prezzo_max.value]
statistiche = filtrato.groupby("neighborhood")["price"].agg(
["count", "mean", "median", "std"]
).round(0)
statistiche
# Cella 4: visualizzazione (anch'essa reattiva)
import altair as alt
chart = (
alt.Chart(filtrato)
.mark_bar()
.encode(
x=alt.X("price:Q", bin=alt.Bin(maxbins=40), title="Prezzo (€)"),
y=alt.Y("count()", title="Numero di immobili"),
color="neighborhood:N",
)
.properties(width=600, height=300)
)
mo.ui.altair_chart(chart)
Il punto cruciale è la Cella 2: prezzo_max è una normale variabile Python, non un widget con callback. Il suo valore corrente vive in prezzo_max.value. Quando l'utente sposta lo slider, Marimo re-invoca automaticamente le celle 3 e 4 perché le sue variabili appaiono nel loro codice. Niente observe(), niente @interact, niente callback hell. È la stessa filosofia di React o Solid.js applicata alle celle di un notebook.
Celle SQL native con DuckDB, Polars e pandas
Marimo integra direttamente il supporto SQL tramite DuckDB, permettendoti di interrogare DataFrame Python, file Parquet, database remoti o Google Sheets con lo stesso motore. Le celle SQL sono anch'esse nodi del DAG: se il DataFrame sorgente cambia, la query si riesegue. Questo elimina il rischio di lavorare su risultati SQL stantii, un problema comune quando si combinano %%sql e Python in Jupyter.
# Cella Python: caricamento (Marimo espone 'df' al motore SQL)
import polars as pl
df = pl.read_parquet("transazioni_2026.parquet")
# Cella SQL (dal menu dell'editor scegli 'SQL Cell')
# Il risultato viene assegnato alla variabile 'top_clienti'
top_clienti = mo.sql(f"""
SELECT
cliente_id,
COUNT(*) AS n_transazioni,
SUM(importo) AS totale_speso,
AVG(importo) AS media_transazione
FROM df
WHERE data >= '2026-01-01'
GROUP BY cliente_id
HAVING SUM(importo) > 10000
ORDER BY totale_speso DESC
LIMIT 100
""")
# Cella Python successiva: il risultato è già un DataFrame Polars
top_clienti.head(10)
Poiché top_clienti è una variabile del DAG, ogni cambio a monte (nuovo file Parquet, filtro modificato) triggera la riesecuzione. Per una guida approfondita al motore analitico, la mia guida completa a DuckDB con Python copre in dettaglio le funzionalità di lettura da Data Lake, query federate e ottimizzazione. Chi arriva da Polars troverà utile anche la guida pratica a Polars 2026, dato che Marimo restituisce i risultati SQL come DataFrame Polars per default nella distribuzione consigliata.
Come convertire un notebook Jupyter in Marimo
La CLI di Marimo include un convertitore che trasforma file .ipynb in file .py Marimo mantenendo la struttura delle celle e i markdown. Il processo è non-distruttivo: il file Jupyter originale resta intatto.
# conversione singola
marimo convert analisi.ipynb > analisi.py
# conversione in batch (esempio con find)
find . -name "*.ipynb" -exec sh -c 'marimo convert "$1" > "${1%.ipynb}.py"' _ {} \;
# apri il risultato per verificare
marimo edit analisi.py
Attenzione: la conversione non risolve automaticamente le violazioni del modello reattivo. Se il tuo notebook Jupyter ridefinisce df in cinque celle, Marimo mostrerà errori statici che dovrai risolvere manualmente. Nella mia esperienza è un lavoro salutare. Spesso rivela bug di ordine di esecuzione nascosti da mesi (io ho beccato in questo modo un notebook di forecasting che dava risultati diversi a ogni Restart & Run All, e il colpevole era una variabile seed ridefinita in tre punti). Il consiglio pratico è convertire un notebook alla volta, eseguire marimo edit, correggere gli errori indicati e testare con marimo run.
Deploy come web app e come script
Un notebook Marimo è sempre tre cose contemporaneamente: un ambiente interattivo, uno script eseguibile e una web app deployabile. Nessuna conversione, nessun tool aggiuntivo. Comando per servire un notebook come app (le celle di codice sono nascoste, gli utenti vedono solo gli output e gli elementi UI):
# serve il notebook come web app su porta 2718
marimo run analisi.py
# specifica host e porta per il deploy dietro reverse proxy
marimo run analisi.py --host 0.0.0.0 --port 8080
# esegui il notebook come script (nessuna UI, output su stdout)
python analisi.py
Per il deploy in produzione, un Dockerfile minimale è sufficiente. Marimo non richiede WSGI o ASGI server esterni: usa uvicorn internamente.
Il flag --headless disabilita l'apertura automatica del browser, essenziale in container. Per dashboard analitiche più elaborate (grafici multi-serie, tabelle interattive, KPI) puoi combinare Marimo con Altair, Plotly o le tecniche descritte nella guida alla visualizzazione dati in Python.
Modalità lazy per notebook costosi
La reattività automatica è ideale per pipeline leggere, ma diventa un problema quando una singola cella impiega minuti (training di modelli, query pesanti, download API). Marimo offre due modalità:
Autorun (default): ogni cambio a monte esegue le celle a valle.
Lazy: le celle a valle vengono marcate come stale e mostrate con un indicatore visivo. Le riesegui tu quando serve.
Puoi attivare la modalità lazy globalmente da marimo edit → Settings → Runtime, oppure per singola cella con il decoratore @app.cell(disabled=True). In pratica, io uso l'autorun per il 90% dei notebook esplorativi e la lazy per quelli di training o ETL. Un pattern che mi ha salvato più di una serata è isolare la parte "costosa" in una funzione @mo.cache con memoizzazione persistente su disco:
@mo.cache
def addestra_modello(X_train, y_train, n_stimatori: int, seed: int):
from sklearn.ensemble import GradientBoostingClassifier
modello = GradientBoostingClassifier(
n_estimators=n_stimatori,
random_state=seed,
)
modello.fit(X_train, y_train)
return modello
# la chiamata è reattiva ma il risultato viene cacheato su disco
modello = addestra_modello(X_train, y_train, n_stimatori=200, seed=42)
Se sposti lo slider "n_stimatori" e torni a un valore precedente, Marimo restituisce il modello dalla cache in millisecondi invece di riaddestrare. Il decoratore usa il contenuto della funzione e i valori degli argomenti come chiave: se modifichi il corpo della funzione la cache viene invalidata automaticamente. Questo pattern è discusso anche nella guida a scikit-learn e pipeline di machine learning.
marimo pair 2026: notebook guidati da agenti AI
La novità più significativa del 2026 è marimo pair, una modalità di sessione che consente ad agenti AI (Claude Code, Codex, OpenCode) di connettersi a un notebook live, leggere lo stato delle variabili in memoria, eseguire celle e proporre modifiche. A differenza degli assistenti "chat only", marimo pair fornisce all'agente il vero contesto runtime: dtypes, shape, distribuzioni, non solo il testo del notebook.
# avvia una sessione condivisa con autenticazione token
marimo pair analisi.py
# output di esempio:
# Session URL: http://localhost:2718/pair/abc123
# Agent token: mp_tok_a1b2c3d4e5f6
# Scope: read-cells, execute-cells, edit-cells
L'agente si connette con il token, richiede scope granulari (sola lettura, esecuzione, modifica) e opera con audit log completo. Il modello reattivo aiuta parecchio: l'agente non deve "ricordarsi" di rieseguire le celle a valle, il DAG lo fa per lui. Nella pratica ho trovato marimo pair utile soprattutto in due casi. Il primo è il refactoring guidato (chiedere all'agente di riscrivere una cella pandas in Polars mantenendo la semantica). Il secondo è la diagnostica (chiedere all'agente di analizzare la distribuzione di una colonna e proporre trasformazioni). Per un confronto approfondito degli strumenti Python moderni con backend condivisi, la guida a NumPy 2.x, vettorizzazione e broadcasting mostra le operazioni che gli agenti generano più frequentemente. Il codice sorgente di Marimo, per chi ama scavare, è ospitato su GitHub sul repository marimo-team/marimo.
Best practice per notebook reattivi in produzione
Dopo un anno di uso quotidiano di Marimo su progetti reali, ecco le regole che ho trovato più utili per mantenere notebook puliti e performanti:
Una variabile, una cella. Non è solo un vincolo del linter: è una disciplina che rende il DAG leggibile. Se una cella cresce troppo, spezzala per concetto (caricamento, pulizia, modello), non per lunghezza.
Usa mo.cache per tutto ciò che costa più di 1 secondo. Training, query di rete, letture di file grandi. La cache persiste tra restart del kernel se configuri @mo.persistent_cache.
Isola i side-effect. Le celle che scrivono su disco o inviano richieste HTTP dovrebbero avere un flag abilita = mo.ui.checkbox(value=False, label="Esegui side-effect"). Evita esecuzioni accidentali durante la modifica del codice a monte.
Type hint sulle variabili pubbliche. Marimo integra Ruff e mypy; annotare i tipi rende il codice comprensibile agli agenti pair e ai colleghi.
Nomi di variabile parlanti. Poiché il DAG è ricavato dai nomi, chiamare df ogni DataFrame porta a conflitti immediati. Usa vendite_grezze, vendite_filtrate, vendite_aggregate.
Formatter automatico. Configura Ruff su save (marimo edit → Settings → Format on save). Diff Git puliti valgono almeno il 30% del beneficio di Marimo.
Test unitari sulle funzioni. Le funzioni di trasformazione dovrebbero vivere in file .py separati importati dal notebook, e coperte da pytest. Il notebook è per l'esplorazione, non per la logica di business.
Per approfondire i pattern statistici che ho menzionato (split di validazione, controllo di ipotesi, distribuzioni di riferimento) la guida all'analisi statistica in Python con SciPy e statsmodels si integra bene con il flusso reattivo Marimo. Tutti i codici di quella guida sono stati inizialmente sviluppati in Marimo prima di essere estratti in moduli.
Domande frequenti
Marimo è pronto per la produzione o è ancora sperimentale?
Marimo è considerato stabile: a inizio 2026 ha superato 20.000 stelle su GitHub ed è usato in produzione da Cloudflare, Shopify e BlackRock. La versione 1.0 è prevista per fine 2026, ma le API della 0.14+ sono considerate stabili e la roadmap pubblica documenta ogni breaking change con largo anticipo.
Posso usare Marimo dentro VS Code o PyCharm?
Sì. Poiché i notebook Marimo sono file .py, qualsiasi IDE li apre nativamente. L'editor web di Marimo aggiunge la visualizzazione reattiva e gli elementi UI, ma puoi modificare le celle in VS Code (le celle sono delimitate da @app.cell) ed eseguire con marimo edit per l'anteprima. Esiste anche un'estensione ufficiale VS Code in beta che integra l'editor Marimo direttamente nell'IDE.
Marimo supporta kernel R o Julia come Jupyter?
No. Marimo è specifico per Python, e la scelta è deliberata perché l'analisi statica del DAG richiede un parser AST del linguaggio host. Se hai bisogno di R o Julia rimani su Jupyter, oppure isola quelle parti in script separati e leggi i loro output in Marimo tramite subprocess o file intermedi.
Come gestisco segreti e credenziali in un notebook Marimo deployato?
Come qualsiasi applicazione Python: variabili d'ambiente lette con os.getenv(), gestore di segreti (AWS Secrets Manager, HashiCorp Vault) o file .env caricato con python-dotenv. Non hardcodare mai credenziali nelle celle, perché il file .py viene versionato in Git e il rischio di leak è reale. Marimo espone anche mo.env per pattern comuni.
Qual è la differenza tra marimo edit, marimo run e python notebook.py?
marimo edit apre l'editor completo con la possibilità di modificare celle; marimo run serve il notebook come web app con celle di codice nascoste (utile per dashboard); python notebook.py lo esegue come script batch senza UI, restituendo il risultato dell'ultima cella. Lo stesso file supporta tutti e tre gli usi senza modifiche.
Marimo funziona con dati troppo grandi per la RAM?
Sì, se usi backend che supportano il lazy evaluation come Polars, DuckDB o Dask. Marimo di per sé non impone limiti: carica in memoria solo ciò che le tue celle materializzano. Il pattern consigliato è query SQL lazy su DuckDB, materializzare solo i risultati aggregati in un DataFrame Polars, poi visualizzare. Vedi la mia guida DuckDB per esempi concreti su Parquet e Data Lake.
Guida pratica a DuckDB in Python nel 2026: installazione, query SQL su DataFrame pandas e Polars, lettura Parquet e S3, window function e benchmark reali contro pandas e Polars.
Polars è la libreria DataFrame piu veloce per Python nel 2026: fino a 30x piu rapida di pandas. Guida pratica con Lazy API, streaming engine, window function, join paralleli e una strategia di migrazione da pandas testata in produzione.