Criando indicador de progresso giratório no console (Python)

Criando indicador de progresso giratório no console (Python)

Overview

Bem-vindo ao fascinante mundo da automação de tarefas com Python! Neste post, você vai aprender a dizer adeus ao monótono efeito Pacman em seus scripts de automação e dar as boas-vindas a um estiloso cursor giratório. Acompanhe-me nesta jornada por códigos simples e eficazes, e prepare-se para elevar suas habilidades de codificação ao próximo nível.

Uma das grandes vantagens do Python é a facilidade para criar scripts de automatização de tarefas repetitivas. É comum estes scripts possuírem um indicador de progresso (exemplo: Processando registro n de 1000). O problema disso é que você pode cair no efeito Pacman e ficar colocando . (pontos) para indicar que algo está sendo feito. Isso fica visualmente feio, alem de atrapalhar a visualização das mensagens, caso o processamento seja longo.

Para resolver isso, vou mostrar como fazer um cursor que fica girando, indicando que algo está sendo processado.

Para exemplificar o efeito pacman, segue um gif

Este é o efeito pacman: Você vai acumulando os pontos e isso vai ficando cada vez mais confuso na hora de ler…

O objeto deste post é mostrar como criar um cursors que fique girando, exemplo:

Fica bem mais limpo, certo?

O exemplo é bem simples e vai ser uma função que você vai chamando em um loop e ela se encarrega de atualizar o cursor e a mensagem.

Pode até ser que já exista uma biblioteca/pacote que faça isso, mas desconheço. De toda forma, é bom aprender como este tipo de abordagem funciona. O desenvolvimento desta função utilizará o pacote sys, então não precisamos instalar nada de novo.

Vamos começar pelo “segredo” do processo. No exemplo:

print("Hi", end="")
print("\rThere", end="")
print("\rBacon")

O resultado no console será:

Bacon

Process finished with exit code 0

Onde foram parar as outras mensagens (“Hi” e “There”)? Bom, elas foram exibidas no console, mas foram imediatamente substituídas por causa do caractere especial \r, que é o return carriage, responsável por voltar o cursor para o início da linha.

No exemplo, utilizei a função print com o argumento end="", indicando que uma string vazia será adicionada ao final de cada mensagem, ao invés da quebra de linha (que é o padrão da função). Para explicar o código de exemplo:

  1. A mensagem “Hi” foi exibida no console sem quebra de linha;
  2. O cursor retornou para o inicio da linha (\r) e a mensagem “There” foi exibida (também sem quebra de linha);
  3. Novamente, o caractere \r foi utilizado e o cursor retornou para o inicio da linha.
  4. A mensagem “Bacon” foi exibida. (Como foi o último print, não coloquei o end="", pois não faria diferença)

Bem simples, certo?

Para fazer cursor que fica girando, vou assumir o mesmo padrão de mensagens que está no gif: [<cursor>] mensagem.

A primeira coisa que precisamos é da sequencia de caracteres que serão utilizados:

CURSOR_POSITIONS = ('\\', '|', '/', '-')
CURRENT_CURSOR_POS = 0

Agora, para facilitar a vida, precisamos de uma função que, sempre que acionada, vai retornar a próxima posição do cursor:

def _get_next_cursor_():
    global CURRENT_CURSOR_POS
    try:
        CURRENT_CURSOR_POS += 1
        return CURSOR_POSITIONS[CURRENT_CURSOR_POS]
    except:
        CURRENT_CURSOR_POS = 0
        return CURSOR_POSITIONS[CURRENT_CURSOR_POS]

Esta função tenta retornar a próxima posição do tuple criado com os caracteres (CURSOR_POSITIONS). Um exception é indicação de que o cursor está na última posição, então a variável CURRENT_CURSOR_POS, que armazena a posição atual do cursor, é definida para zero, retornando a “animação” para o início.

O próximo (e último) passo é criar a função que mostra o cursor girando + mensagem:

def spinning_cursor_with_label(label_text):
    sys.stdout.write('\r[{}]\t{}'.format(_get_next_cursor_(), label_text))
    sys.stdout.flush()

Explicando o funcionamento dela:

  1. A mensagem que será exibida é formatada, colocando na primeira posição (primeiro {}) o caractere referente ao cursor e na segunda posição, a mensagem;
  2. É feita uma chamada para a função write (stdout.write), informando a mensagem que será exibida. Note que ela começa com um \r, ou seja, antes de exibir a mensagem, o cursor sempre vai retornar para o inicio da linha;
  3. A função flush (stdout.flush) é chamada para forçar o buffer que guarda as mensagens a exibi-las na tela;

Qual a diferença entre o print e o stdout.write? A grosso modo, o print é um encapsulamento da função stdout.write.

Pronto. Você tem um cursor que gira e não precisa mais cair no efeito pacman!

Para fazer o exemplo exibido no gif:

for i in range(25):
    spinning_cursor_with_label(label_text="Processing thing {}...".format(i))

Este exemplo está no meu Github.

Espero ter ajudado!