Substitua a saída do console em Python

106

Estou me perguntando como poderia criar um desses contadores de console bacanas em Python, como em certos programas C / C ++.

Eu tenho um loop fazendo coisas e a saída atual é ao longo das linhas de:

Doing thing 0
Doing thing 1
Doing thing 2
...

o que seria mais legal seria apenas atualizar a última linha;

X things done.

Já vi isso em vários programas de console e estou me perguntando se / como faria isso em Python.

dutt
fonte
3
Você deveria dar uma olhada nas maldições .
Björn Pollex
2
Basta usar print: stackoverflow.com/a/8436827/1959808
Ioannis Filippidis
1
@ BjörnPollex, cursesé um exagero (veja a resposta aceita).
Alexey

Respostas:

151

Uma solução fácil é apenas escrever "\r"antes da string e não adicionar uma nova linha; se a string nunca ficar mais curta, isso é suficiente ...

sys.stdout.write("\rDoing thing %i" % i)
sys.stdout.flush()

Um pouco mais sofisticada é uma barra de progresso ... isso é algo que estou usando:

def startProgress(title):
    global progress_x
    sys.stdout.write(title + ": [" + "-"*40 + "]" + chr(8)*41)
    sys.stdout.flush()
    progress_x = 0

def progress(x):
    global progress_x
    x = int(x * 40 // 100)
    sys.stdout.write("#" * (x - progress_x))
    sys.stdout.flush()
    progress_x = x

def endProgress():
    sys.stdout.write("#" * (40 - progress_x) + "]\n")
    sys.stdout.flush()

Você chama de startProgresspassar a descrição da operação, então progress(x)onde xestá a porcentagem e finalmenteendProgress()

6502
fonte
2
E se a corda for mais curta que a anterior?
math2001
6
@ math2001 preenchimento com espaços em branco.
felipsmartins
Votado para apenas as 2 primeiras linhas de código. A parte da barra de progresso está se tornando lenta em alguns casos. De qualquer forma, obrigado @ 6502
WaterRocket8236
Alguns programas ( restic, flatpak) podem atualizar várias linhas de saída do console. Você sabe por acaso como isso pode ser alcançado?
Alexey
1
@Alexey: você pode usar códigos de escape ANSI para mover o cursor, limpar partes da tela e alterar cores ... consulte en.wikipedia.org/wiki/ANSI_escape_code
6502
39

Uma solução mais elegante poderia ser:

def progressBar(current, total, barLength = 20):
    percent = float(current) * 100 / total
    arrow   = '-' * int(percent/100 * barLength - 1) + '>'
    spaces  = ' ' * (barLength - len(arrow))

    print('Progress: [%s%s] %d %%' % (arrow, spaces, percent), end='\r')

chame esta função com valuee endvalue, o resultado deve ser

Progress: [------------->      ] 69 %

Nota: versão Python 2.x aqui .

Aravind Voggu
fonte
Você deve usar o Halo para obter melhores barras de progresso e controles giratórios.
Aravind Voggu
17

No python 3, você pode fazer isso para imprimir na mesma linha:

print('', end='\r')

Especialmente útil para acompanhar as últimas atualizações e o progresso.

Eu também recomendaria tqdm daqui se alguém quiser ver o progresso de um loop. Ele imprime a iteração atual e o total de iterações como uma barra de progressão com um tempo previsto de conclusão. Super útil e rápido. Funciona para python2 e python3.

Joop
fonte
7

A outra resposta pode ser melhor, mas aqui está o que eu estava fazendo. Primeiro, fiz uma função chamada progresso que imprime o caractere de retrocesso:

def progress(x):
    out = '%s things done' % x  # The output
    bs = '\b' * 1000            # The backspace
    print bs,
    print out,

Então eu chamei em um loop em minha função principal assim:

def main():
    for x in range(20):
        progress(x)
    return

É claro que isso apagará toda a linha, mas você pode mexer com ela para fazer exatamente o que deseja. Acabei fazendo uma barra de progresso usando este método.

Bryce Siedschlaw
fonte
4
Funciona, mas se a linha anterior tinha mais caracteres do que a próxima, os caracteres após o final da nova linha permanecem da linha anterior: "Registro de verificação ortográfica 417/701 [serviço alterado para superfície] quando] uminescência] cência] shmentarianismo] "
Lil 'Bits
7

Para qualquer um que se deparar com isso anos depois (como eu fiz), ajustei um pouco os métodos do 6502 para permitir que a barra de progresso diminuísse e aumentasse. Útil em mais casos. Obrigado 6502 por uma ótima ferramenta!

Basicamente, a única diferença é que toda a linha de #s e -s é escrita cada vez que progress (x) é chamado, e o cursor sempre é retornado ao início da barra.

def startprogress(title):
    """Creates a progress bar 40 chars long on the console
    and moves cursor back to beginning with BS character"""
    global progress_x
    sys.stdout.write(title + ": [" + "-" * 40 + "]" + chr(8) * 41)
    sys.stdout.flush()
    progress_x = 0


def progress(x):
    """Sets progress bar to a certain percentage x.
    Progress is given as whole percentage, i.e. 50% done
    is given by x = 50"""
    global progress_x
    x = int(x * 40 // 100)                      
    sys.stdout.write("#" * x + "-" * (40 - x) + "]" + chr(8) * 41)
    sys.stdout.flush()
    progress_x = x


def endprogress():
    """End of progress bar;
    Write full bar, then move to next line"""
    sys.stdout.write("#" * 40 + "]\n")
    sys.stdout.flush()
jat255
fonte
1
No entanto, descobri que isso pode causar lentidão se for chamado com muita frequência pelo código, então acho que YMMV
jat255
6

Se entendi bem (não tenho certeza) quer imprimir usando <CR>e não <LR>?

Se for assim, isso é possível, desde que o terminal do console permita isso (ele será interrompido quando a saída si for redirecionada para um arquivo).

from __future__ import print_function
print("count x\r", file=sys.stdout, end=" ")
Sorin
fonte
5

Isso pode ser feito sem usar a biblioteca sys se olharmos para a print()função

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

Aqui está o meu código:

def update(n):
    for i in range(n):
        print("i:",i,sep='',end="\r",flush=True)
        #time.sleep(1)
Suman Saurabh
fonte
5

Eu escrevi isso há um tempo e muito feliz com isso. Sinta-se livre para usá-lo.

Leva um indexe totale opcionalmente titleou bar_length. Uma vez feito isso, substitui a ampulheta por uma marca de seleção.

⏳ Calculating: [████░░░░░░░░░░░░░░░░░░░░░] 18.0% done

✅ Calculating: [█████████████████████████] 100.0% done

Incluí um exemplo que pode ser executado para testá-lo.

import sys
import time

def print_percent_done(index, total, bar_len=50, title='Please wait'):
    '''
    index is expected to be 0 based index. 
    0 <= index < total
    '''
    percent_done = (index+1)/total*100
    percent_done = round(percent_done, 1)

    done = round(percent_done/(100/bar_len))
    togo = bar_len-done

    done_str = '█'*int(done)
    togo_str = '░'*int(togo)

    print(f'\t⏳{title}: [{done_str}{togo_str}] {percent_done}% done', end='\r')

    if round(percent_done) == 100:
        print('\t✅')


r = 50
for i in range(r):
    print_percent_done(i,r)
    time.sleep(.02)

Eu também tenho uma versão com barra de progresso responsiva dependendo da largura do terminal usando shutil.get_terminal_size()se for de interesse.

Ivan Procopovich
fonte
4

Adicionou um pouco mais de funcionalidade ao exemplo do Aravind Voggu :

def progressBar(name, value, endvalue, bar_length = 50, width = 20):
        percent = float(value) / endvalue
        arrow = '-' * int(round(percent*bar_length) - 1) + '>'
        spaces = ' ' * (bar_length - len(arrow))
        sys.stdout.write("\r{0: <{1}} : [{2}]{3}%".format(\
                         name, width, arrow + spaces, int(round(percent*100))))
        sys.stdout.flush()
        if value == endvalue:     
             sys.stdout.write('\n\n')

Agora você pode gerar várias barras de progresso sem substituir a anterior.

Eu também adicionei namecomo um valor com uma largura fixa.

Para dois loops e duas vezes, o uso do progressBar()resultado será semelhante a:

animação da barra de progresso

Nils Kohlmey
fonte
-1

O código abaixo contará a mensagem de 0 a 137 a cada 0,3 segundo, substituindo o número anterior.

Número do símbolo no backstage = número de dígitos.

stream = sys.stdout
for i in range(137):
    stream.write('\b' * (len(str(i)) + 10))
    stream.write("Message : " + str(i))
    stream.flush()
    time.sleep(0.3)
laggerok19
fonte