Dica 24 - Barra de progresso
Versões usadas no vídeo: esta dica não usa Django; Python 3.8, progress 1.5, tqdm 4.56, click 7.1.2, progressbar2 3.53.1, clint 0.5.1 e httpx 0.16.1 (no Jupyter Notebook).
- progress
- tqdm
- click - click progressbar
- progressbar 2
- clint
- with sys
- gist rg3915
- progressbar on Jupyter notebook
Leia mais em How to Easily Use a Progress Bar in Python
Quando um script demora (importar uma planilha, processar milhares de registros, baixar um arquivo), uma barra de progresso mostra ao usuário que o programa não travou e quanto ainda falta. Neste tutorial vamos ver oito jeitos de fazer uma barra de progresso em Python: cinco bibliotecas prontas (progress, tqdm, click, progressbar2 e clint), dois jeitos só com a biblioteca padrão (sys) e, por fim, a barra dentro do Jupyter Notebook, inclusive acompanhando o download de um arquivo.
Todos os exemplos seguem a mesma ideia: um laço for de 100 voltas com um sleep curto, que faz o papel do "trabalho de verdade" (no lugar do sleep entraria, por exemplo, o processamento de cada linha de um arquivo).
Pré-requisitos
- Python 3 (no vídeo, Python 3.8) com um ambiente virtual ativado.
- Uma pasta para os exemplos. No vídeo ela se chama
progressbar, dentro do repositório dicas-de-django:
python -m venv .venv
source .venv/bin/activate
mkdir progressbar
cd progressbar
Cada biblioteca é instalada na hora em que for usada. Se preferir instalar tudo de uma vez, com as versões do vídeo:
pip install progress==1.5 tqdm==4.56.0 click==7.1.2 progressbar2==3.53.1 clint==0.5.1
1. progress
A biblioteca progress é a mais simples: você cria uma Bar com um texto e chama bar.next() a cada passo.
pip install progress
# example01_progress.py
from time import sleep
from progress.bar import Bar
with Bar('Processing...') as bar:
for i in range(100):
sleep(0.02)
bar.next()
Bar('Processing...')cria a barra com o rótulo que aparece à esquerda. Por padrão, o total é 100 (parâmetromax).- Usar
withgarante que a barra seja finalizada (bar.finish()) no fim do bloco. bar.next()avança um passo.
$ python example01_progress.py
Processing... |################################| 100/100
2. tqdm
O tqdm é provavelmente a biblioteca de barra de progresso mais usada em Python. Basta "embrulhar" qualquer iterável com tqdm().
pip install tqdm
No vídeo foi instalado o tqdm-4.56.0.
# example02_tqdm.py
from tqdm import tqdm
from time import sleep
for i in tqdm(range(100)):
sleep(0.02)
# Do something
$ python example02_tqdm.py
100%|██████████| 100/100 [00:02<00:00, 36.78it/s]
Além da porcentagem e da barra, o tqdm mostra o tempo decorrido, o tempo estimado que falta e a velocidade (iterações por segundo).
3. click
O click é uma biblioteca para criar interfaces de linha de comando (usamos ele na dica 20), e ele tem a sua própria barra de progresso.
pip install click
# example03_click.py
import click
from time import sleep
# Fill character is # by default, you can change it
# for any other char you want, or even change the color.
fill_char = click.style('=', fg='yellow')
with click.progressbar(range(100), label='Loading...', fill_char=fill_char) as bar:
for i in bar:
sleep(0.02)
click.style('=', fg='yellow')cria o caractere de preenchimento: um sinal de igual em amarelo (fgé a cor do texto, foreground). O padrão seria#.click.progressbar()recebe o iterável, o rótulo (label) e o caractere de preenchimento (fill_char), e é usado comwith. Dentro do bloco, você percorrebarem vez dorange.
$ python example03_click.py
Loading... [====================================] 100%
4. progressbar2
pip install progressbar2
Atenção: o pacote se chama progressbar2 no pip, mas o módulo importado é progressbar.
# example04_progressbar2.py
from time import sleep
from progressbar import progressbar
for i in progressbar(range(100)):
sleep(0.02)
$ python example04_progressbar2.py
100% (100 of 100) |######################| Elapsed Time: 0:00:02 Time: 0:00:02
5. clint
O clint é um conjunto de ferramentas para aplicações de terminal. O módulo clint.textui.progress tem dois estilos: a barra normal (progress.bar) e o progress.mill, um indicador que fica girando na tela (| / - \) ao lado do contador.
pip install clint
# example05_clint.py
from time import sleep
from clint.textui import progress
print('Clint - Regular Progress Bar')
for i in progress.bar(range(100)):
sleep(0.02)
print('Clint - Mill Progress Bar')
for i in progress.mill(range(100)):
sleep(0.02)
$ python example05_clint.py
Clint - Regular Progress Bar
[################################] 100/100 - 00:00:02
Clint - Mill Progress Bar
100/100
Durante a execução do segundo laço, o caractere antes do contador vai girando.
6. Só com Python, usando sys
Dá para fazer uma barra de progresso sem instalar nada, só com a biblioteca padrão. Esta versão é baseada nesta resposta do Stack Overflow:
# example06_sys.py
import time
import sys
toolbar_width = 40
# setup toolbar
sys.stdout.write("[%s]" % (" " * toolbar_width))
sys.stdout.flush()
sys.stdout.write("\b" * (toolbar_width + 1)) # return to start of line, after '['
for i in range(toolbar_width):
time.sleep(0.1) # do real work here
# update the bar
sys.stdout.write("-")
sys.stdout.flush()
sys.stdout.write("]\n") # this ends the progress bar
Como funciona:
- Primeiro escrevemos a "moldura" vazia:
[+ 40 espaços +]. sys.stdout.flush()força o texto a aparecer na tela na hora (sem ele, o Python guarda a saída num buffer)."\b"é o caractere de backspace: escrevê-lo 41 vezes volta o cursor para logo depois do[.- A cada volta do laço escrevemos um
-, que vai preenchendo a moldura por cima dos espaços. - No fim,
"]\n"fecha a barra e pula a linha.
$ python example06_sys.py
[----------------------------------------]
Cuidado com a digitação: no vídeo, a variável foi declarada primeiro como toolbar_with (sem o "d"), e a execução deu erro:
Traceback (most recent call last):
File "example06_sys.py", line 7, in <module>
sys.stdout.write("[%s]" % (" " * toolbar_width))
NameError: name 'toolbar_width' is not defined
O erro aponta a linha 7, mas o problema está na linha 4, onde a variável foi criada com o nome errado. Corrigindo para toolbar_width, funciona.
7. Uma função de barra de progresso reutilizável (gist)
Esta é a versão que está no gist do Regis: uma função geradora progressbar() que você usa em volta de qualquer lista ou range, como o tqdm, só que sem dependências.
# example07_sys.py
import sys
import time
def progressbar(it, prefix="", size=60, file=sys.stdout):
count = len(it)
def show(j):
x = int(size * j / count)
file.write("%s[%s%s] %i/%i\r" %
(prefix, "#" * x, "." * (size - x), j, count))
file.flush()
show(0)
for i, item in enumerate(it):
yield item
show(i + 1)
file.write("\n")
file.flush()
users = ['Regis', 'Abel', 'Eduardo', 'Elaine']
for user in progressbar(users, "Processing: "):
time.sleep(0.1)
# Do something.
for i in progressbar(range(42), "Processing: "):
time.sleep(0.05)
# Do something.
Explicando a função:
ité o iterável (precisa ter tamanho, por causa dolen(it));prefixé o texto antes da barra;sizeé a largura da barra em caracteres;fileé onde escrever (a saída padrão).show(j)desenha a barra no passoj: calcula quantos#cabem (x = int(size * j / count)), completa o resto com.e mostra o contadorj/count. O\r(carriage return) volta o cursor para o início da linha, então cada desenho sobrescreve o anterior.show(0)desenha a barra vazia; depois, para cada item, a função fazyield item(devolve o item para o seufor) e redesenha a barra comshow(i + 1).- No fim,
\npula a linha para a barra não ser sobrescrita pelo próximo texto.
No exemplo, a primeira barra percorre a lista de nomes (4 itens) e a segunda percorre um range de 0 a 42:
$ python example07_sys.py
Processing: [############################################################] 4/4
Processing: [############################################################] 42/42
8. Barra de progresso no Jupyter Notebook
O tqdm também funciona dentro do Jupyter Notebook, com uma barra gráfica (verde quando termina).
$ jupyter notebook
No navegador, clique em New > Python 3 para criar um notebook. Na primeira célula, importe o tqdm de um jeito que funciona tanto no notebook quanto num script comum:
# progressbar_jupyter.ipynb (célula 1)
import sys
if hasattr(sys.modules["__main__"], "get_ipython"):
from tqdm import notebook as tqdm
else:
import tqdm
from time import sleep
Se o código estiver rodando dentro do IPython/Jupyter (o módulo __main__ tem a função get_ipython), usamos tqdm.notebook, que desenha a barra como um widget; senão, o tqdm normal, de texto. Rode a célula com Shift + Enter.
# célula 2
n = 0
for i in tqdm.trange(100):
n += 1
sleep(0.01)
tqdm.trange(100) é um atalho para tqdm.tqdm(range(100)). A saída é uma barra verde:
100% ████████████████████ 100/100 [00:01<00:00, 92.26it/s]
Acompanhando um download
Um uso bem prático: mostrar o progresso do download de um arquivo. Vamos baixar o código-fonte do Python 3.9.0 com o httpx:
# célula 3
url = "https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz"
# célula 4
import httpx
Se o httpx não estiver instalado, essa célula dá ModuleNotFoundError: No module named 'httpx'. Dá para instalar sem sair do notebook, numa célula nova:
%pip install httpx
No vídeo foi instalado o httpx-0.16.1. Depois da instalação, reinicie o kernel (menu Kernel > Restart & Run All) para que o pacote novo seja reconhecido, apague a célula do %pip e rode tudo de novo.
# célula 5
with httpx.stream("GET", url) as response:
total = int(response.headers["Content-Length"])
with tqdm.tqdm(total=total) as progress:
for chunk in response.iter_bytes():
progress.update(len(chunk))
httpx.stream()faz a requisição sem baixar tudo para a memória de uma vez.- O cabeçalho
Content-Lengthdiz o tamanho total do arquivo em bytes; ele vira ototalda barra. response.iter_bytes()entrega o arquivo em pedaços (chunks); a cada pedaço,progress.update(len(chunk))avança a barra pelo número de bytes recebidos.
Enquanto o arquivo baixa, a barra mostra algo como 63% ... 16752028/26724009 [00:04<00:02, ...] e chega a 100% no fim.
Aqui o exemplo só mostra o progresso; para salvar o arquivo, você escreveria cada chunk num arquivo aberto em modo wb.
Resumo
| Jeito | Instalação | Uso |
|---|---|---|
| progress | pip install progress |
with Bar('...') as bar: + bar.next() |
| tqdm | pip install tqdm |
for i in tqdm(iteravel): |
| click | pip install click |
with click.progressbar(iteravel) as bar: |
| progressbar2 | pip install progressbar2 |
for i in progressbar(iteravel): |
| clint | pip install clint |
progress.bar(iteravel) ou progress.mill(iteravel) |
| sys | nada | sys.stdout.write + flush |
| função do gist | nada | for item in progressbar(iteravel, "Processing: "): |
| Jupyter | pip install tqdm |
from tqdm import notebook as tqdm |
Observação: o clint foi arquivado pelo autor e não recebe mais atualizações; para projetos novos, o tqdm é a escolha mais comum.