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.

29 Sep 2026 8 min de leitura Equipe Python Dev BR

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 e Jupyter, 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, em jobs de Airflow e em scripts de automação de planilhas.

Instalação em ambiente virtual

Sempre instale dentro de um ambiente virtual:

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

Com uv:

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:

from tqdm.auto import tqdm

Uso básico: envolva o iterável

O padrão de 95% dos casos:

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:

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

Três detalhes úteis já nesta forma:

ArgumentoFunção
descTexto à esquerda da barra (“Validando NF-e”)
unitRótulo da unidade ("arquivo", "linha", "req")
totalTamanho quando o iterável não expõe __len__
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:

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.

Postfix e mensagens sem quebrar a barra

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

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:

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:

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:

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 e com openpyxl / Excel depois que os dados já estão em DataFrame. Para volumes grandes em que apply em linha é o gargalo, avalie vetorização ou Polars — a barra ajuda a medir, não a otimizar.

Jupyter Notebook e JupyterLab

Em notebooks, prefira sempre:

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:

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

No Jupyter Lab moderno 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 e multiprocessing, a regra é: uma única barra no processo pai, atualizada conforme os workers devolvem resultados.

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.

Opções que mais aparecem no dia a dia

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:

export TQDM_DISABLE=1
python job.py

Ou no código:

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ériotqdmRich Progress
Instalação e APIMínima, um wrappingMais verbosa, mais poderosa
NotebooksExcelente via tqdm.autoPossível, menos idiomático
EcossistemaPadrão em ML, ETL, scriptsMelhor se o app já usa Rich
VisualClássico de terminalTabelas, spinners, painéis
DependênciasQuase nenhumaTraz 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.

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.

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, agende com APScheduler e você tem um job observável sem framework pesado. Para empacotar e entregar ao time financeiro no Windows, siga o guia de PyInstaller.

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, CSV na prática, pandas do zero e agendar o job. Se a automação precisar de interface com botão em vez de terminal, o caminho é o Tkinter.

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados