Remplacer la sortie de la console en Python

106

Je me demande comment je pourrais créer un de ces compteurs de console astucieux en Python comme dans certains programmes C / C ++.

J'ai une boucle qui fait des choses et la sortie actuelle est le long des lignes de:

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

ce qui serait mieux serait d'avoir juste la dernière mise à jour de ligne;

X things done.

J'ai vu cela dans un certain nombre de programmes de console et je me demande si / comment je ferais cela en Python.

dutt
la source
3
Vous devriez jeter un œil aux malédictions .
Björn Pollex
2
Il suffit d'utiliser print: stackoverflow.com/a/8436827/1959808
Ioannis Filippidis
1
@ BjörnPollex, cursesest une exagération (voir la réponse acceptée).
Alexey

Réponses:

151

Une solution simple consiste simplement à écrire "\r"avant la chaîne et à ne pas ajouter de nouvelle ligne; si la chaîne ne devient jamais plus courte, cela suffit ...

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

Une barre de progression est légèrement plus sophistiquée ... c'est quelque chose que j'utilise:

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

Vous appelez en startProgresspassant la description de l'opération, puis progress(x)xest le pourcentage et enfinendProgress()

6502
la source
2
Et si la chaîne est plus courte que la précédente?
math2001
6
@ math2001 remplissage avec des espaces.
felipsmartins
Voté pour seulement 2 premières lignes de code. La partie barre de progression devient lente dans quelques cas. En tout cas merci @ 6502
WaterRocket8236
Certains programmes ( restic, flatpak) peuvent mettre à jour plusieurs lignes de sortie de console. Savez-vous par hasard comment cela peut être réalisé?
Alexey
1
@Alexey: vous pouvez utiliser les codes d'échappement ANSI pour déplacer le curseur, effacer des parties de l'écran et changer les couleurs ... voir en.wikipedia.org/wiki/ANSI_escape_code
6502
39

Une solution plus élégante pourrait être:

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

appeler cette fonction avec valueet endvalue, le résultat doit être

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

Remarque: version Python 2.x ici .

Aravind Voggu
la source
Vous devez utiliser Halo pour de meilleures barres de progression et des fileurs.
Aravind Voggu le
18

En python 3, vous pouvez le faire pour imprimer sur la même ligne:

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

Particulièrement utile pour suivre les dernières mises à jour et progrès.

Je recommanderais également tqdm à partir d'ici si l'on veut voir la progression d'une boucle. Il imprime l'itération actuelle et les itérations totales sous forme de barre de progression avec un temps prévu de fin. Super utile et rapide. Fonctionne pour python2 et python3.

Joop
la source
7

L'autre réponse est peut-être meilleure, mais voici ce que je faisais. Tout d'abord, j'ai créé une fonction appelée progression qui imprime le caractère de retour arrière:

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

Ensuite, je l'ai appelé en boucle dans ma fonction principale comme ceci:

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

Cela effacera bien sûr toute la ligne, mais vous pouvez jouer avec elle pour faire exactement ce que vous voulez. J'ai fini par créer une barre de progression en utilisant cette méthode.

Bryce Siedschlaw
la source
4
Fonctionne, mais si la ligne précédente avait plus de caractères que la suivante, les caractères après la fin de la nouvelle ligne restent de la ligne précédente: "Enregistrement de vérification orthographique 417/701 [service changé en surface] lorsque] uminescence] cence] shmentarisme] "
Lil 'Bits
7

Pour tous ceux qui tombent sur cette année plus tard (comme je l'ai fait), j'ai un peu modifié les méthodes de 6502 pour permettre à la barre de progression de diminuer et d'augmenter. Utile dans un peu plus de cas. Merci 6502 pour un excellent outil!

Fondamentalement, la seule différence est que toute la ligne de #s et -s est écrite à chaque fois que progress (x) est appelé et que le curseur est toujours renvoyé au début de la barre.

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
la source
1
J'ai trouvé cependant que cela peut provoquer des ralentissements s'il est appelé trop fréquemment par le code, donc je suppose que YMMV
jat255
6

Si j'ai bien compris (pas sûr) que vous voulez imprimer en utilisant <CR>et non <LR>?

Si c'est possible, tant que le terminal de la console le permet (il se cassera lorsque la sortie sera redirigée vers un fichier).

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

Cela peut être fait sans utiliser la bibliothèque sys si nous regardons la print()fonction

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

Voici mon code:

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

J'ai écrit ceci il y a quelque temps et je suis vraiment content. Sentez-vous libre de l'utiliser.

Il faut un indexet totalet éventuellement titleou bar_length. Une fois terminé, remplace le sablier par une coche.

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

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

J'ai inclus un exemple qui peut être exécuté pour le tester.

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)

J'ai également une version avec une barre de progression réactive en fonction de la largeur du terminal en utilisant shutil.get_terminal_size()si cela est intéressant.

Ivan Procopovitch
la source
4

Ajout d'un peu plus de fonctionnalités à l'exemple d' 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')

Vous pouvez désormais générer plusieurs barres de progression sans remplacer la précédente.

J'ai également ajouté nameune valeur avec une largeur fixe.

Pour deux boucles et deux fois l'utilisation du progressBar()résultat ressemblera à:

animation de la barre de progression

Nils Kohlmey
la source
-1

Le code ci-dessous comptera le message de 0 à 137 chaque 0,3 seconde remplaçant le numéro précédent.

Nombre de symboles dans les coulisses = nombre de chiffres.

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
la source