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.


3
Deberías echarle un vistazo a las maldiciones .
Björn Pollex


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()


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í .


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.


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.


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()

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=" ")

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)

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.


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


-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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.