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 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:
ETAajuda 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:
| 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__ |
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é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.
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
printno meio do loop — usetqdm.writeouset_postfix.- Generator sem
total=— a barra não mostra percentual nem ETA confiável. - Várias barras sem
position/leave— o terminal vira um painel quebrado. - Barra em log de arquivo — desligue com
disable=TrueouTQDM_DISABLE=1. - Medir performance pelo tqdm — a barra tem custo pequeno; em loops de microssegundos por item, atualize com
miniters/minintervalmaiores.
Checklist rápido
- Instalei
tqdmno venv (não no Python do sistema)? - Envolvi o iterável ou usei
updatecomtotal=? - Passei
desceunitlegíveis para quem vai olhar o terminal? - Troquei
printportqdm.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.