Reemplazar la salida de la consola en Python

106

Me pregunto cómo podría crear uno de esos ingeniosos contadores de consola en Python como en ciertos programas C / C ++.

Tengo un bucle haciendo cosas y la salida actual está en las líneas de:

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

lo que sería mejor sería tener la última línea actualizada;

X things done.

He visto esto en varios programas de consola y me pregunto si / cómo haría esto en Python.

holandés
fuente
3
Deberías echarle un vistazo a las maldiciones .
Björn Pollex
2
Solo use print: stackoverflow.com/a/8436827/1959808
Ioannis Filippidis
1
@ BjörnPollex, curseses una exageración (vea la respuesta aceptada).
Alexey

Respuestas:

151

Una solución fácil es escribir "\r"antes de la cadena y no agregar una nueva línea; si la cuerda nunca se acorta, esto es suficiente ...

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

Un poco más sofisticada es una barra de progreso ... esto es algo que estoy 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()

Llamas startProgresspasando la descripción de la operación, luego progress(x)donde xestá el porcentaje y finalmenteendProgress()

6502
fuente
2
¿Y si la cuerda es más corta que la anterior?
math2001
6
@ math2001 relleno con espacios en blanco.
felipsmartins
Votado solo por las 2 primeras líneas de código. La parte de la barra de progreso se está volviendo lenta en algunos casos. De todos modos, gracias @ 6502
WaterRocket8236
Algunos programas ( restic, flatpak) pueden actualizar varias líneas de salida de la consola. ¿Sabe por casualidad cómo se puede lograr esto?
Alexey
1
@Alexey: puede usar códigos de escape ANSI para mover el cursor, borrar partes de la pantalla y cambiar colores ... ver en.wikipedia.org/wiki/ANSI_escape_code
6502
39

Una solución más elegante podría 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')

llamar a esta función con valuey endvalue, el resultado debe ser

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

Nota: Versión de Python 2.x aquí .

Aravind Voggu
fuente
Debes usar Halo para mejorar las barras de progreso y las ruedas giratorias.
Aravind Voggu
17

En python 3 puede hacer esto para imprimir en la misma línea:

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

Especialmente útil para realizar un seguimiento de la última actualización y el progreso.

También recomendaría tqdm desde aquí si uno quiere ver el progreso de un bucle. Imprime la iteración actual y las iteraciones totales como una barra de progresión con un tiempo esperado de finalización. Super útil y rápido. Funciona para python2 y python3.

Joop
fuente
7

La otra respuesta puede ser mejor, pero esto es lo que estaba haciendo. Primero, hice una función llamada progreso que imprime el carácter de retroceso:

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

Luego lo llamé en un bucle en mi función principal así:

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

Esto, por supuesto, borrará toda la línea, pero puedes jugar con ella para hacer exactamente lo que quieras. Terminé haciendo una barra de progreso usando este método.

Bryce Siedschlaw
fuente
4
Funciona, pero si la línea anterior tenía más caracteres que la siguiente, los caracteres después del final de la nueva línea permanecen de la línea anterior: "Registro de revisión ortográfica 417/701 [servicio cambiado a superficie] cuando] uminescence] cence] shmentarianism] "
Lil 'Bits
7

Para cualquiera que se encuentre con esto años más tarde (como lo hice yo), modifiqué un poco los métodos del 6502 para permitir que la barra de progreso disminuya y aumente. Útil en un poco más de casos. ¡Gracias 6502 por una gran herramienta!

Básicamente, la única diferencia es que la línea completa de #s y -s se escribe cada vez que se llama a progress (x), y el cursor siempre se devuelve al inicio de la 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
fuente
1
Sin embargo, descubrí que esto puede causar algunas ralentizaciones si el código lo llama con demasiada frecuencia, así que supongo que YMMV
jat255
6

Si lo entendí bien (no estoy seguro), ¿desea imprimir usando <CR>y no <LR>?

Si es así, esto es posible, siempre que el terminal de la consola lo permita (se interrumpirá cuando la salida sea redirigida a un archivo).

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

Se puede hacer sin usar la biblioteca sys si miramos la print()función

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

Aquí está mi código:

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

Escribí esto hace un tiempo y estoy realmente feliz con él. Sientase libre de usarlo.

Toma un indexy totaly opcionalmente titleo bar_length. Una vez hecho esto, reemplaza el reloj de arena con una marca de verificación.

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

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

Incluí un ejemplo que se puede ejecutar para probarlo.

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)

También tengo una versión con barra de progreso receptiva dependiendo del ancho del terminal que use shutil.get_terminal_size()si eso es de interés.

Ivan Procopovich
fuente
4

Se agregó un poco más de funcionalidad al ejemplo de 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')

Ahora puede generar múltiples barras de progreso sin reemplazar la anterior.

También he agregado namecomo valor con un ancho fijo.

Para dos bucles y dos veces, el uso del progressBar()resultado se verá así:

animación de la barra de progreso

Nils Kohlmey
fuente
-1

El siguiente código contará el mensaje de 0 a 137 cada 0,3 segundos reemplazando el número anterior.

Número de símbolo al 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
fuente