Marimo Notebooks em Python: Guia Completo do Notebook Reativo em 2026

Marimo é o notebook Python reativo que substitui o Jupyter: instale, use SQL com DuckDB, publique no navegador via WASM e versione tudo no Git com arquivos .py puros.

Marimo Notebooks Python: Guia Reativo 2026

Atualizado: 10 de agosto de 2026

Marimo é um notebook reativo para Python em que qualquer alteração numa célula reexecuta automaticamente todas as células que dependem dela, elimina o estado oculto do Jupyter e é armazenado como .py puro, o que o torna versionável no Git, executável como script e implantável como aplicativo web. Neste guia mostro como instalar o marimo, criar notebooks reprodutíveis, usar células SQL com DuckDB, publicar em WASM e migrar do Jupyter em 2026, com exemplos que rodam sem adaptação.

  • Marimo executa células por grafo de dependências: alterar uma variável reexecuta todas as células que a consomem, em ordem topológica.
  • Notebooks são arquivos .py legíveis, o que elimina conflitos de merge típicos do formato .ipynb.
  • Instalação básica é pip install marimo; para células SQL e recursos extras use pip install "marimo[sql]".
  • O comando marimo run notebook.py serve o notebook como app interativo com o código Python oculto.
  • Você pode publicar sem servidor com marimo export html-wasm, que roda Python no navegador via Pyodide.
  • Em 2026 o marimo ganhou plugin para PyCharm, marimo pair para agentes de IA e integrações Quarto/MDX.

O que é marimo notebook em Python?

Marimo é um notebook Python de código aberto que troca o modelo imperativo do Jupyter por um runtime reativo determinístico: cada célula declara as variáveis que produz e consome, o runtime monta um DAG das dependências e reexecuta só o que precisa quando algo muda. Isso significa que o notebook nunca depende da ordem em que você clicou nas células, o que resolve, de uma vez, a categoria inteira de bugs de "célula fora de ordem" que atormentam quem entrega análises em Jupyter há mais de uma década.

O projeto foi lançado em 2024 pela marimo Inc. e ganhou tração séria em 2025 e 2026 dentro de times de dados que precisam entregar não só análises exploratórias, mas também dashboards internos e pipelines reproduzíveis. Honestamente, uso marimo em dois cenários bem diferentes no meu dia a dia: como caderno de exploração para pipelines de reconciliação em Airflow (onde a reatividade evita que eu execute um groupby caro em cima de um df que já não existe mais) e como camada de UI para engenheiros de dados iterarem sobre queries DuckDB sem escrever HTML. O ponto chave é que o mesmo arquivo .py serve para as três coisas: notebook editável, script agendado e aplicativo web publicado, sem reescrita.

Como instalar marimo em 2026

Para instalar marimo, execute pip install marimo num Python 3.9 ou superior. Se você usa uv (que é o que recomendo em 2026, porque resolve o environment em milissegundos), o comando é uv add marimo dentro de um projeto criado com uv init. A versão básica já inclui o editor reativo, exportação HTML e integração com matplotlib, plotly e altair, mas para desbloquear células SQL, completions de IA e o visualizador interativo de DataFrames você deve instalar o extra recomendado.

# Instalação mínima
pip install marimo

# Instalação recomendada (SQL cells, IA, dataframe viewer)
pip install "marimo[recommended]"

# Só o extra de SQL (DuckDB + drivers)
pip install "marimo[sql]"

# Com uv (10-100x mais rápido que pip)
uv add "marimo[recommended]"

# Roda o tutorial interativo
marimo tutorial intro

Depois de instalar, o comando marimo edit abre um servidor local (padrão em http://127.0.0.1:2718) onde você cria e edita notebooks. Para criar um arquivo específico direto do terminal, use marimo edit analise_vendas.py. Se o arquivo não existir, o marimo cria um esqueleto .py válido. Confirme a versão instalada com marimo --version; a série 0.10.x é a atual de meados de 2026 e recebe releases praticamente semanais no repositório oficial no GitHub.

Reatividade: como marimo elimina bugs de estado oculto

A pergunta que ouço mais em code review é: "por que reatividade importa se o Jupyter tem Run All?". A resposta curta é que Run All resolve o sintoma, não a causa. No Jupyter, o kernel mantém um espaço de nomes global mutável, e nada impede que você defina df na célula 3, redefina na célula 7 e volte a usar o df antigo na célula 5. O resultado depende inteiramente da ordem de execução, e o arquivo .ipynb não guarda essa ordem: ele guarda apenas o output que estava na tela quando você salvou.

O marimo faz três coisas para eliminar essa classe de bug. Primeiro, proíbe redefinição de variáveis entre células: se duas células declaram df, o notebook não roda e o editor mostra o conflito. Segundo, analisa o AST de cada célula para determinar quais variáveis são lidas e escritas, montando o grafo de dependências antes de qualquer execução. Terceiro, ao rodar uma célula, marca automaticamente todas as células descendentes como stale e as reexecuta (ou espera que você aprove, se você desativou a auto-execução em células caras).

import marimo as mo
import pandas as pd

# Célula 1: carrega dados brutos
df_raw = pd.read_parquet("s3://bucket/eventos.parquet")

# Célula 2: filtragem
# Se você mudar o filtro, a Célula 3 reexecuta sozinha.
df_filtrado = df_raw[df_raw["evento"] == "compra"]

# Célula 3: agregação (depende de df_filtrado)
receita_por_dia = (
    df_filtrado
    .groupby(pd.Grouper(key="ts", freq="1D"))["valor"]
    .sum()
    .rename("receita_brl")
)

Na prática, isso significa que quando eu troco o filtro na célula 2, a agregação na célula 3 e qualquer gráfico downstream reexecutam automaticamente e ficam consistentes com o novo estado. Já economizei mais de uma noite de plantão graças a isso. Antes, um dashboard errado no Jupyter demorava horas até alguém perceber que a fonte tinha sido redefinida três células acima. Se você usa muito .groupby() em séries temporais e quer um refresher sobre desempenho com dados sujos, tenho um material relacionado sobre detecção de outliers em Python com pandas e scikit-learn que costuma ser um bom próximo passo depois de dominar o modelo reativo.

Marimo vs Jupyter: qual a diferença na prática?

A comparação abaixo resume os oito eixos em que times reais tomam decisão entre os dois. Não é uma escolha binária. Muitos times mantêm Jupyter para exploração ad-hoc e migram para marimo quando o notebook começa a virar produto interno.

DimensãoJupyter Notebook / LabMarimo
Formato de arquivoJSON (.ipynb) com outputs embutidosPython puro (.py) sem outputs
Modelo de execuçãoImperativo, ordem depende do usuárioReativo por DAG, ordem topológica
Redefinição de variáveisPermitida (fonte comum de bugs)Proibida em tempo de análise
Diffs no GitRuins (JSON + base64 de imagens)Limpos como qualquer script Python
Elementos de UIipywidgets com callbacks manuaisNativos, reativos, sem callbacks
SQLVia mágicas %%sql ou libs externasCélulas SQL nativas com DuckDB
Deploy como appVoilà, Panel ou Streamlit à partemarimo run nativo
Renderização no GitHubVisualização inline do .ipynbAparece como script (menos amigável)

Escolha Jupyter quando você quer só um scratchpad rápido, quando o time inteiro já vive no Colab, ou quando o consumidor final da análise precisa ver outputs pré-computados dentro do GitHub sem clonar o repositório. Escolha marimo quando o notebook vai virar dashboard interno, quando você quer executar o mesmo arquivo como job noturno em Airflow, ou quando reprodutibilidade é um requisito compliance-driven (coisa cada vez mais comum em times regulados). O marimo não resolve todos os problemas, mas resolve o mais caro: notebooks que "funcionam na minha máquina" e explodem em produção.

Células SQL com DuckDB e conexões nativas

Uma das funcionalidades que mais uso é a célula SQL nativa. Diferente das mágicas %%sql do Jupyter, no marimo você clica em "Add SQL cell", escreve SQL puro, e por baixo o marimo serializa aquilo como uma chamada mo.sql(). Ou seja, o notebook continua sendo Python válido. O resultado da consulta volta como DataFrame Polars (padrão) ou pandas, e participa do mesmo grafo reativo que o resto do notebook.

import marimo as mo
import polars as pl

# Célula Python: define o dataframe eventos
eventos = pl.read_parquet("eventos_2026.parquet")

# Célula SQL: o marimo detecta que eventos vem da célula anterior
# e a query interpola o valor de um slider da UI
_df = mo.sql(
    f"""
    SELECT
        cliente_id,
        COUNT(*) AS n_eventos,
        SUM(valor_brl) AS receita
    FROM eventos
    WHERE ts >= '{data_inicio.value}'
    GROUP BY cliente_id
    HAVING receita > {limite_receita.value}
    ORDER BY receita DESC
    LIMIT 50
    """
)

O backend padrão é DuckDB, o que dá desempenho analítico embutido de graça, mas o botão "Add Database Connection" no editor também abre conexões para PostgreSQL, MySQL, SQLite, Snowflake e BigQuery, guardando credenciais em variáveis de ambiente. Se você já usa DuckDB para análise local, o que já cobri em detalhes no meu guia sobre DuckDB com Python para análise de dados, o marimo é o companheiro natural para transformar essas consultas em dashboards. E se seu pipeline usa dbt em cima do warehouse, escrever um notebook marimo que valida os modelos antes do deploy vira uma prática que evita muito rework. Combinei isso com testes de expectativa como os que descrevo em Great Expectations em Python e o retrabalho caiu bastante.

Elementos de UI reativos sem callbacks

Os elementos de UI do marimo (mo.ui.slider, mo.ui.dropdown, mo.ui.date, mo.ui.table, mo.ui.file) se comportam como variáveis Python normais. Quando o usuário arrasta um slider, o marimo trata isso exatamente como se você tivesse editado a célula que definiu o slider: todas as células dependentes reexecutam. Não existe on_change=, não existe callback aninhado, não existe estado paralelo. Essa é a diferença mais visível em relação ao ipywidgets, que exige um handler manual para cada widget.

import marimo as mo
import altair as alt

# Célula 1: UI
data_inicio = mo.ui.date(
    value="2026-01-01",
    label="Data inicial da análise",
)
limite_receita = mo.ui.slider(
    start=0, stop=10_000, step=100, value=500,
    label="Receita mínima (BRL)",
)
mo.vstack([data_inicio, limite_receita])

# Célula 2: reativa aos widgets
grafico = (
    alt.Chart(receita_por_dia.reset_index())
    .mark_line()
    .encode(x="ts:T", y="receita_brl:Q")
    .properties(width=700, height=280)
)
grafico

Essa arquitetura reativa é o que permite que o mesmo notebook seja publicado como app com um único comando. Vale destacar que a célula que renderiza o widget precisa ser a última expressão da célula: o marimo trata o último valor como o output visual, exatamente como o Jupyter. E se você retorna uma lista ou dicionário de widgets, o marimo desestrutura em uma grade automática, o que evita CSS manual para layout.

Deploy: app web, script e WASM no navegador

O marimo tem três modos de execução que compartilham o mesmo arquivo .py: modo editor (marimo edit), modo app (marimo run) e modo script (python notebook.py). O modo app remove a UI de código e serve o notebook como aplicação interativa numa porta HTTP, perfeito para dashboards internos atrás de um proxy reverso ou um túnel Cloudflare. O modo script executa o notebook do início ao fim como se fosse um .py convencional, o que é útil em Airflow, Prefect ou GitHub Actions.

# Modo app (dashboard interativo)
marimo run analise_vendas.py --host 0.0.0.0 --port 8080

# Modo script (para agendadores)
python analise_vendas.py

# Exporta como HTML estático com dados congelados
marimo export html analise_vendas.py -o build/analise.html

# Exporta como aplicativo WASM (roda 100% no navegador)
marimo export html-wasm analise_vendas.py -o build/wasm/

O modo WASM é a novidade que me impressionou mais em 2026. O comando marimo export html-wasm empacota o notebook num diretório estático que pode ser servido por qualquer CDN (GitHub Pages, Cloudflare Pages, S3 + CloudFront). Dentro do navegador do usuário, o marimo carrega Pyodide, uma porta do CPython para WebAssembly, e executa o notebook inteiro no cliente, sem backend Python. NumPy, SciPy, scikit-learn, pandas, matplotlib, polars e duckdb já vêm pré-instalados no Pyodide, e pacotes puros de Python podem ser adicionados via micropip. Isso torna trivial publicar tutoriais interativos, playgrounds educacionais e demos de modelos sem operar um servidor.

Como converter notebooks Jupyter para marimo

A migração de um .ipynb existente é feita com marimo convert. O comando lê o JSON do Jupyter, preserva o conteúdo de cada célula, remove os outputs (que não fazem sentido em Python puro) e produz um .py que pode ser aberto direto no editor marimo. A conversão não resolve automaticamente redefinições de variáveis: se o notebook original definia df em cinco células, você vai precisar renomear ou consolidar. Na minha experiência migrando notebooks de time, cerca de 80% dos arquivos precisam de duas ou três correções mínimas para rodar.

# Converte um único notebook
marimo convert analise_antiga.ipynb -o analise_marimo.py

# Converte em lote (com xargs)
find notebooks/ -name "*.ipynb" | \
  xargs -I{} marimo convert {} -o {}.py

# Abre para revisão e ajustes
marimo edit analise_marimo.py

Um ponto que ainda pega gente desprevenida: notebooks Jupyter que dependem de efeitos colaterais (por exemplo, uma célula que chama display(df) só para efeito visual) precisam ser reestruturados. No marimo, cada célula tem uma única expressão de saída, e efeitos colaterais devem virar chamadas explícitas a mo.output.append() ou mo.vstack([...]). Isso obriga um mínimo de disciplina, mas é o mesmo tipo de disciplina que separa notebooks descartáveis de código que sobrevive à sprint. Se você está migrando também os DataFrames para uma engine mais rápida, meu guia sobre como migrar do pandas para o Polars é útil como referência complementar, já que o marimo suporta as duas engines de forma equivalente.

Como versionar marimo notebooks no Git

Como notebooks marimo são .py puros, o versionamento no Git é trivial: git diff mostra as mudanças reais de código, não deltas de JSON com base64 de imagens. Isso destrava três coisas que eram dor de cabeça no Jupyter: revisão de PR sem plugins, resolução de conflitos de merge sem nbstripout, e blame linha a linha que faz sentido.

Recomendo três configurações no .pre-commit-config.yaml do repositório: rodar ruff check nos notebooks (o marimo entende Ruff nativamente), rodar marimo check para validar que o DAG está consistente, e opcionalmente exportar o HTML de cada notebook num CI job para publicação automática em Pages. O marimo check pega, por exemplo, referências a variáveis que não existem, dependências cíclicas entre células, e imports não usados, um pré-filtro barato que evita PRs quebrados. Times que também usam uv costumam adicionar um passo que roda uv sync e python notebook.py no CI para garantir execução end-to-end.

Perguntas Frequentes

O que é um notebook marimo?

Um notebook marimo é um arquivo Python (.py) executado por um runtime reativo que monta um grafo de dependências entre células e reexecuta automaticamente todas as células que dependem de uma variável alterada. Ele substitui o modelo imperativo do Jupyter por execução topológica determinística.

Marimo é gratuito e de código aberto?

Sim. O marimo é distribuído sob a licença Apache 2.0 no repositório oficial no GitHub. A empresa marimo Inc. mantém o projeto e oferece serviços pagos opcionais como o molab (notebooks hospedados) e integrações empresariais, mas o núcleo é livre para uso comercial.

Marimo substitui completamente o Jupyter?

Para trabalho de produto (dashboards internos, pipelines reproduzíveis, notebooks versionados no Git) o marimo substitui com vantagem. Para exploração descartável, ensino via Google Colab, ou apresentações em GitHub que exigem outputs pré-renderizados, o Jupyter ainda é preferível. Muitos times mantêm ambos.

Como executar um notebook marimo como aplicativo web?

Use marimo run notebook.py --host 0.0.0.0 --port 8080. O comando serve o notebook como app interativo, com o código Python oculto e apenas os elementos de UI e outputs visíveis. Para expor externamente, coloque atrás de um proxy reverso (nginx, Caddy) com TLS.

Marimo funciona com pandas, Polars e PyArrow?

Sim, com os três. O visualizador de DataFrame do marimo reconhece pandas, Polars e PyArrow nativamente, e as células SQL retornam Polars por padrão (com opção de pandas). Isso torna o marimo uma escolha neutra em relação à engine subjacente.

Sobre o Autor Sofia Castellanos

Sofia is a Python data engineer with 7 years building ingestion and transformation systems for media and adtech. She spent three years at Spotify on the personalization-data team, where she shipped a streaming-to-batch reconciliation pipeline that processes around 90 billion playback events per day, and two years before that at The New York Times on the subscriber-analytics platform. She focuses her writing on production pandas patterns (chunked reads, categorical memory tricks, Arrow interop), Airflow 2.x task groups, and the kinds of dbt + Python hybrid pipelines that show up once your warehouse bill stops being cute. She also maintains pyspark-helpers, a small library for column-name munging she keeps porting between jobs. Sofia is based in Madrid, originally from Bogota, and a relentless defender of type hints in notebook code.