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:
- A mensagem “Hi” foi exibida no console sem quebra de linha;
- O cursor retornou para o inicio da linha (\r) e a mensagem “There” foi exibida (também sem quebra de linha);
- Novamente, o caractere \r foi utilizado e o cursor retornou para o inicio da linha.
- 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:
- A mensagem que será exibida é formatada, colocando na primeira posição (primeiro {}) o caractere referente ao cursor e na segunda posição, a mensagem;
- É 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;
- 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!
Latest posts by Breno RdV (see all)
- O que é Metaclass e como ela funciona. (#python #dev #metaclass) - janeiro 11, 2023
- Entenda a mágica dos Generators. (#python, #dev, #generator, #iterator) - dezembro 28, 2022
- Ordenando um DataFrame por múltiplas colunas. (#python #pandas #jupyter #dev #data) - agosto 3, 2022