---
title: "tqdm em Python: barra de progresso para loops e automações"
url: "https://python.dev.br/blog/tqdm-barra-de-progresso-python/"
markdown_url: "https://python.dev.br/blog/tqdm-barra-de-progresso-python.MD"
description: "Aprenda a usar o tqdm em Python para criar barras de progresso em loops, pandas, notebooks Jupyter, downloads e automações: instalação, opções úteis, barras aninhadas e quando preferir o Rich."
date: "2026-09-29"
author: "Equipe Python Dev BR"
---

# tqdm em Python: barra de progresso para loops e automações

Aprenda a usar o tqdm em Python para criar barras de progresso em loops, pandas, notebooks Jupyter, downloads e automações: instalação, opções úteis, barras aninhadas e quando preferir o Rich.


**Para criar uma barra de progresso em Python, instale o `tqdm` (`pip install tqdm`) e envolva o iterável: `for item in tqdm(lista): ...`.** Em poucos segundos o terminal mostra percentual, velocidade (it/s) e tempo estimado — o suficiente para saber se aquele ETL de notas fiscais, o download de CSVs do dados.gov.br ou o `apply` no pandas ainda vai demorar dois minutos ou duas horas.

Quem pergunta a um assistente *"como fazer barra de progresso em Python?"* ou *"como usar tqdm com pandas?"* precisa de mais do que o hello world: instalação limpa, opções que importam no dia a dia (`desc`, `unit`, `total`, `leave`, `disable`), barras aninhadas, integração com [pandas](/blog/introducao-ao-pandas/) e [Jupyter](/guias/configurando-jupyter-notebook/), e quando trocar o tqdm pelo Rich. É o que este guia cobre, com exemplos voltados a automações e pipelines comuns no Brasil.

## Por que uma barra de progresso importa

Um `for` silencioso em um arquivo de 200 mil linhas parece travado. Em produção isso vira ticket no Slack; em entrevista técnica, parece amadorismo. A barra resolve três coisas de uma vez:

- **Feedback humano**: a pessoa na frente do notebook sabe que o processo está vivo.
- **Estimativa de tempo**: `ETA` ajuda a decidir se vale esperar ou matar o job.
- **Diagnóstico**: velocidade caindo no meio do loop costuma apontar I/O, rede ou contenção — não "Python lento".

O `tqdm` (do árabe *taqaddum*, "progresso") virou o padrão de fato da comunidade: aparece em tutoriais de [ETL](/blog/etl-python-2026/), em jobs de [Airflow](/blog/airflow-python-orquestracao-pipelines/) e em scripts de [automação de planilhas](/blog/python-para-automacao-de-planilhas/).

## Instalação em ambiente virtual

Sempre instale dentro de um [ambiente virtual](/guias/criando-virtual-environment/):

```bash
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install tqdm
```

Com [uv](/blog/uv-gerenciador-pacotes-python/):

```bash
uv add tqdm
```

Para notebooks, o mesmo pacote serve. Em Jupyter Lab / Notebook, a variante `tqdm.auto` escolhe automaticamente a barra de widget quando faz sentido:

```python
from tqdm.auto import tqdm
```

## Uso básico: envolva o iterável

O padrão de 95% dos casos:

```python
from tqdm import tqdm
from time import sleep

arquivos = [f"nota_{i:04d}.xml" for i in range(120)]

for nome in tqdm(arquivos, desc="Validando NF-e"):
    sleep(0.02)  # simula parse/validação
```

Saída típica no terminal:

```text
Validando NF-e: 100%|██████████| 120/120 [00:02<00:00, 49.12it/s]
```

Três detalhes úteis já nesta forma:

| Argumento | Função |
|---|---|
| `desc` | Texto à esquerda da barra ("Validando NF-e") |
| `unit` | Rótulo da unidade (`"arquivo"`, `"linha"`, `"req"`) |
| `total` | Tamanho quando o iterável não expõe `__len__` |

```python
for linha in tqdm(abrir_stream(), total=50_000, unit="linha", desc="Lendo CSV"):
    processar(linha)
```

Se o iterável tem tamanho conhecido (`list`, `range`, a maioria dos DataFrames), o `tqdm` descobre sozinho. Generators, cursores de banco e respostas HTTP em streaming pedem `total=` explícito.

## Atualização manual com `update`

Quando o progresso não vem de um `for` simples — por exemplo, um download com chunks — use o context manager:

```python
from pathlib import Path

import httpx
from tqdm import tqdm


def baixar(url: str, destino: Path) -> None:
    with httpx.stream("GET", url, timeout=60.0, follow_redirects=True) as resposta:
        resposta.raise_for_status()
        total = int(resposta.headers.get("Content-Length", 0))
        destino.parent.mkdir(parents=True, exist_ok=True)

        with destino.open("wb") as arquivo, tqdm(
            total=total,
            unit="B",
            unit_scale=True,
            unit_divisor=1024,
            desc=destino.name,
        ) as barra:
            for pedaco in resposta.iter_bytes(chunk_size=64 * 1024):
                arquivo.write(pedaco)
                barra.update(len(pedaco))
```

`unit_scale=True` transforma bytes em KiB/MiB automaticamente. Para clientes HTTP mais resilientes (timeouts e retries), combine com o guia de [HTTPX](/blog/httpx-timeouts-retries-python/).

## Postfix e mensagens sem quebrar a barra

`print` dentro do loop danifica a barra. Prefira `set_postfix` / `set_description`:

```python
erros = 0
for caminho in tqdm(arquivos, desc="Importando"):
    try:
        importar(caminho)
    except ValueError as exc:
        erros += 1
        # atualiza o texto à direita sem pular linha
        tqdm.write(f"falha em {caminho}: {exc}")
    # métrica viva na própria barra
    # (reabra a barra como variável se precisar chamar set_postfix)
```

Forma idiomática com a barra nomeada:

```python
with tqdm(arquivos, desc="Importando") as barra:
    erros = 0
    for caminho in barra:
        ok = importar(caminho)
        if not ok:
            erros += 1
        barra.set_postfix(erros=erros, ultimo=Path(caminho).name)
```

`tqdm.write(...)` imprime acima da barra sem corrompê-la — use no lugar de `print`.

## Barras aninhadas

Pipelines em duas dimensões (pastas × arquivos, meses × dias) pedem barras aninhadas. O `tqdm` posiciona cada uma com `position` e `leave`:

```python
meses = ["2026-01", "2026-02", "2026-03"]

for mes in tqdm(meses, desc="Meses", position=0):
    dias = listar_dias(mes)
    for dia in tqdm(dias, desc=f"Dias {mes}", position=1, leave=False):
        processar_dia(mes, dia)
```

`leave=False` na barra interna evita que centenas de barras mortas poluam o terminal. Em notebooks, `tqdm.auto` costuma lidar com o aninhamento sem `position`.

## Integração com pandas

Trocar `apply` silencioso por `progress_apply` é o atalho que mais economiza tempo em análise de dados:

```python
from tqdm.auto import tqdm
import pandas as pd

tqdm.pandas(desc="Normalizando CPF")

df = pd.read_csv("clientes.csv", dtype=str)
df["cpf"] = df["cpf"].progress_apply(lambda x: "".join(ch for ch in x if ch.isdigit()))
```

Funciona bem com [leitura de CSV](/blog/python-csv-leitura-escrita-arquivos/) e com [openpyxl / Excel](/blog/python-e-excel-openpyxl/) depois que os dados já estão em DataFrame. Para volumes grandes em que `apply` em linha é o gargalo, avalie vetorização ou [Polars](/blog/polars-alternativa-pandas-python/) — a barra ajuda a medir, não a otimizar.

## Jupyter Notebook e JupyterLab

Em notebooks, prefira sempre:

```python
from tqdm.auto import tqdm
```

`auto` seleciona `tqdm.notebook` (widget HTML) quando detecta Jupyter e cai no tqdm de terminal no console. Se a barra HTML não renderizar, instale o widget e recarregue:

```bash
pip install tqdm ipywidgets
jupyter nbextension enable --py widgetsnbextension  # notebooks clássicos
```

No [Jupyter Lab moderno](/comparacoes/jupyter-notebook-vs-jupyterlab-vs-google-colab/) os widgets costumam funcionar sem o `nbextension`. Em Google Colab, `tqdm.auto` também funciona na maioria dos runtimes.

## Threads, processos e asyncio

O tqdm não "paraleliza" nada — ele só observa. Em [threads](/blog/python-threading-threadpoolexecutor/) e [multiprocessing](/blog/python-multiprocessing/), a regra é: **uma única barra no processo pai**, atualizada conforme os workers devolvem resultados.

```python
from concurrent.futures import ThreadPoolExecutor, as_completed
from tqdm import tqdm


def baixar_um(url: str) -> str:
    # ... download ...
    return url


urls = carregar_lista()
with ThreadPoolExecutor(max_workers=8) as pool, tqdm(total=len(urls), desc="Downloads") as barra:
    futuros = {pool.submit(baixar_um, u): u for u in urls}
    for futuro in as_completed(futuros):
        futuro.result()
        barra.update(1)
```

Para asyncio, o pacote oferece `tqdm.asyncio.tqdm.gather` em versões recentes, ou você atualiza a barra manualmente a cada `Task` concluída. Em workers Celery/Redis o padrão muda: a barra no worker não aparece para quem disparou o job — use logs estruturados e o guia de [tarefas em background](/blog/fastapi-background-tasks-celery-redis-2026/).

## Opções que mais aparecem no dia a dia

```python
for item in tqdm(
    itens,
    desc="Sincronizando",
    unit="reg",
    mininterval=0.5,   # não redesenha mais que 2x por segundo
    dynamic_ncols=True,  # ajusta à largura do terminal
    leave=True,          # mantém a barra ao terminar
    disable=None,        # None = desliga se a saída não for TTY
    colour="green",      # em terminais compatíveis
):
    ...
```

Desligar em CI ou em logs redirecionados:

```bash
export TQDM_DISABLE=1
python job.py
```

Ou no código:

```python
import sys
from tqdm import tqdm

for x in tqdm(dados, disable=not sys.stderr.isatty()):
    ...
```

## tqdm versus Rich Progress

Os dois resolvem barra de progresso. A escolha prática:

| Critério | tqdm | Rich Progress |
|---|---|---|
| Instalação e API | Mínima, um wrapping | Mais verbosa, mais poderosa |
| Notebooks | Excelente via `tqdm.auto` | Possível, menos idiomático |
| Ecossistema | Padrão em ML, ETL, scripts | Melhor se o app já usa Rich |
| Visual | Clássico de terminal | Tabelas, spinners, painéis |
| Dependências | Quase nenhuma | Traz o Rich inteiro |

Recomendação direta: **comece com tqdm**. Migre para `rich.progress` se o mesmo script já imprime tabelas Rich ou se você precisa de várias tarefas nomeadas com estados customizados no mesmo painel. Para interfaces de terminal mais ricas (TUI), o caminho é o [Textual](/blog/textual-tui-terminal-python/).

## Exemplo prático: reprocessar XMLs de NF-e

Cenário comum em fintechs e contabilidades brasileiras: varrer uma pasta de XMLs, validar e gravar um resumo em CSV.

```python
from pathlib import Path
import csv

from tqdm import tqdm

PASTA = Path("nfe_entrada")
SAIDA = Path("nfe_resumo.csv")


def resumir_xml(caminho: Path) -> dict:
    # parse simplificado — no projeto real use a lib de NF-e da equipe
    texto = caminho.read_text(encoding="utf-8", errors="ignore")
    return {
        "arquivo": caminho.name,
        "tamanho": caminho.stat().st_size,
        "tem_cnpj": "CNPJ" in texto,
    }


arquivos = sorted(PASTA.glob("*.xml"))
SAIDA.parent.mkdir(parents=True, exist_ok=True)

with SAIDA.open("w", newline="", encoding="utf-8") as fp:
    writer = csv.DictWriter(fp, fieldnames=["arquivo", "tamanho", "tem_cnpj"])
    writer.writeheader()

    with tqdm(arquivos, desc="NF-e", unit="xml") as barra:
        erros = 0
        for caminho in barra:
            try:
                writer.writerow(resumir_xml(caminho))
            except OSError as exc:
                erros += 1
                tqdm.write(f"erro em {caminho.name}: {exc}")
            barra.set_postfix(erros=erros)
```

Troque a pasta por um caminho do [pathlib](/blog/python-pathlib-manipulacao-caminhos-arquivos/), agende com [APScheduler](/blog/apscheduler-agendar-tarefas-python/) e você tem um job observável sem framework pesado. Para empacotar e entregar ao time financeiro no Windows, siga o guia de [PyInstaller](/blog/pyinstaller-transformar-script-python-exe/).

## Erros comuns

1. **`print` no meio do loop** — use `tqdm.write` ou `set_postfix`.
2. **Generator sem `total=`** — a barra não mostra percentual nem ETA confiável.
3. **Várias barras sem `position`/`leave`** — o terminal vira um painel quebrado.
4. **Barra em log de arquivo** — desligue com `disable=True` ou `TQDM_DISABLE=1`.
5. **Medir performance pelo tqdm** — a barra tem custo pequeno; em loops de microssegundos por item, atualize com `miniters` / `mininterval` maiores.

## Checklist rápido

- [ ] Instalei `tqdm` no venv (não no Python do sistema)?
- [ ] Envolvi o iterável ou usei `update` com `total=`?
- [ ] Passei `desc` e `unit` legíveis para quem vai olhar o terminal?
- [ ] Troquei `print` por `tqdm.write` / `set_postfix`?
- [ ] Em pandas, usei `tqdm.pandas()` + `progress_apply`?
- [ ] Em notebook, importei de `tqdm.auto`?
- [ ] Em CI, desliguei a barra?

## Conclusão

O **tqdm** é a resposta padrão para *"como faço uma barra de progresso em Python?"*: um wrapping no iterável, opções claras e integrações prontas com pandas e Jupyter. Use-o em automações, ETLs e notebooks; reserve o Rich para quando o visual do terminal já depende dele; e combine com ambiente virtual, HTTPX e empacotamento em EXE quando for entregar o script para outra pessoa.

Próximos passos naturais neste site: [pathlib para caminhos](/blog/python-pathlib-manipulacao-caminhos-arquivos/), [CSV na prática](/blog/python-csv-leitura-escrita-arquivos/), [pandas do zero](/blog/introducao-ao-pandas/) e [agendar o job](/blog/apscheduler-agendar-tarefas-python/). Se a automação precisar de interface com botão em vez de terminal, o caminho é o [Tkinter](/blog/tkinter-criando-interfaces-graficas-python/).
