Comment écraser l'impression précédente sur stdout en python?

113

Si j'avais le code suivant:

for x in range(10):
     print x

J'aurais la sortie de

1
2
etc..

Ce que je voudrais faire, c'est au lieu d'imprimer une nouvelle ligne, je veux remplacer la valeur précédente et l'écraser avec la nouvelle valeur sur la même ligne.

ccwhite1
la source
En remarque, assurez-vous d'obtenir la réponse capable de rincer toute la ligne si la ligne précédente est plus longue que la ligne actuelle.
Fruit le

Réponses:

119

Une méthode consiste à utiliser le caractère retour chariot ( '\r') pour revenir au début de la ligne sans passer à la ligne suivante:

for x in range(10):
    print '{0}\r'.format(x),
print

La virgule à la fin de l'instruction d'impression lui indique de ne pas passer à la ligne suivante. La dernière instruction d'impression passe à la ligne suivante afin que votre invite n'écrase pas votre sortie finale.

Mettre à jour

Maintenant que Python 2 est EOL, une réponse Python 3 a plus de sens. Pour Python 3.5 et versions antérieures:

for x in range(10):
    print('{}\r'.format(x), end="")
print()

Dans Python 3.6 et versions ultérieures, les f-strings se lisent mieux:

for x in range(10):
    print(f'{x}\r', end="")
print()
Mike DeSimone
la source
Cela fonctionne, mais je ne comprends pas quelques-unes des parties ... '{0}' et .format
ccwhite1
7
Cela est introduit dans Python 2.6 et (on me le dit) est le moyen standard de formater les chaînes en Python 3; l' %opérateur est obsolète. Fondamentalement, toutes les chaînes ont maintenant une méthode de format . Dans ce cas, le {0}signifie "le premier argument à format()" (le comptage commence à 0).
Mike DeSimone
Ce n'est pas une option si vous devez utiliser un logiciel Windows uniquement et que vous n'avez pas les côtelettes pour exécuter une machine virtuelle.
Mike DeSimone
2
Et si vous voulez être cross-terminal, re.sub(r'\$<\d+>[/*]?', '', curses.tigetstr('el') or '')vous donnera la chaîne d'effacement à la fin de ligne correcte pour le terminal de l'utilisateur. N'oubliez pas d'initialiser cursesavec quelque chose comme curses.setupterm(fd=sys.stdout.fileno()), et utilisez sys.stdout.isatty()pour vous assurer que votre sortie n'est pas redirigée vers un fichier. Voir code.activestate.com/recipes/475116 pour un module Python complet avec contrôle du curseur et prise en charge des couleurs.
Mike DeSimone
1
@PhilMacKay: J'ai essayé ça en python sur mon Mac: Dans [3]: print "Chose à effacer \ r",; time.sleep (1); print "-------------- \ r", -------------- Je pense que votre problème est Windows et ses différentes lignes se terminent. Essayez ceci: import curses; curses.setupterm(fd=sys.stdout.fileno()); print(hex(curses.tigetstr('cr')));Il devrait imprimer les codes hexadécimaux de la séquence de caractères pour aller au début de la ligne.
Mike DeSimone
107

Puisque je me suis retrouvé ici via Google mais que j'utilise Python 3, voici comment cela fonctionnerait dans Python 3:

for x in range(10):
    print("Progress {:2.1%}".format(x / 10), end="\r")

Réponse connexe ici: Comment puis-je supprimer la nouvelle ligne après une instruction d'impression?

Pascal
la source
3
J'ai essayé cela et cela fonctionne très bien. Le seul problème est que cela n'efface pas la sortie précédente sur cette ligne, par exemple: si votre première impression en boucle testinget votre deuxième impression en boucle, testla sortie après la deuxième passe sera toujours testing- maintenant je vois que @ Nagasaki45 l'a souligné
Eric Uldall
15
dans un cahier ipython, je dois faire:print("\r", message, end="")
grisaitis
1
Cela fonctionne très bien, cela n'efface pas la ligne mais l'utilisation de suffisamment d'espaces blancs dans l'impression suivante fait le travail (bien que ce ne soit pas élégant). Un commentaire que je pourrais ajouter est que cela ne fonctionne pas dans la console de débogage de VS Code, mais cela fonctionne dans le terminal.
PhilMacKay
32

La réponse @Mike DeSimone fonctionnera probablement la plupart du temps. Mais...

for x in ['abc', 1]:
    print '{}\r'.format(x),

-> 1bc

Ceci est dû au fait que le '\r'seul retourne au début de la ligne mais n'efface pas la sortie.

EDIT: meilleure solution (que mon ancienne proposition ci-dessous)

Si le support POSIX vous suffit, ce qui suit effacera la ligne courante et laisserait le curseur à son début:

print '\x1b[2K\r',

Il utilise le code d'échappement ANSI pour effacer la ligne du terminal. Plus d'informations peuvent être trouvées dans wikipedia et dans ce grand discours .

Ancienne réponse

La solution (pas si bonne) que j'ai trouvée ressemble à ceci:

last_x = ''
for x in ['abc', 1]:
    print ' ' * len(str(last_x)) + '\r',
    print '{}\r'.format(x),
    last_x = x

-> 1

Un avantage est qu'il fonctionnera également sur Windows.

Nagasaki45
la source
1
J'ai créé une fonction à partir de la `` vieille réponse '' qui fonctionne correctement (même sous Windows), voir ma réponse: stackoverflow.com/a/43952192/965332
erb
25

J'avais la même question avant de visiter ce fil. Pour moi, le sys.stdout.write ne fonctionnait que si je vidais correctement le tampon ie

for x in range(10):
    sys.stdout.write('\r'+str(x))
    sys.stdout.flush()

Sans vidage, le résultat est imprimé uniquement à la fin du script

utilisateur2793078
la source
Aussi bonne idée de remplir le reste de la chaîne avec des espaces comme stringvar.ljust(10,' ')dans le cas de chaînes de longueur variable.
Annarfych
@chris, Don et al. c'est Python 2, la plupart des autres réponses sont Python 3. Tout le monde devrait utiliser Python 3 maintenant; Python 2 est en fin de vie en 2020.
smci
18

Supprimez la nouvelle ligne et imprimez \r.

print 1,
print '\r2'

ou écrivez dans stdout:

sys.stdout.write('1')
sys.stdout.write('\r2')
Ignacio Vazquez-Abrams
la source
9

Essaye ça:

import time
while True:
    print("Hi ", end="\r")
    time.sleep(1)
    print("Bob", end="\r")
    time.sleep(1)

Cela a fonctionné pour moi. La end="\r"pièce lui fait écraser la ligne précédente.

AVERTISSEMENT!

Si vous imprimez hi, puis imprimez en helloutilisant \r, vous obtiendrez hilloparce que la sortie a écrit sur les deux lettres précédentes. Si vous imprimez hiavec des espaces (qui n'apparaissent pas ici), alors il sortira hi. Pour résoudre ce problème, imprimez les espaces à l'aide de \r.

Sam Bernstein
la source
1
Cela ne fonctionne pas pour moi. J'ai copié ce code, mais ma sortie est Hi BobHi BobHi BobHi BobHi Bob. Python 3.7.1
PaulMag
1
Mais la réponse de Rubixred a fonctionné. Bizarre. Ils ressemblent à la même méthode pour moi. La seule différence que je vois est que \rc'est au début plutôt qu'à la fin.
PaulMag
7

Je ne pouvais faire fonctionner aucune des solutions de cette page pour IPython , mais une légère variation de la solution de @ Mike-Desimone a fait le travail: au lieu de terminer la ligne avec le retour chariot, commencez la ligne avec le retour chariot:

for x in range(10):
    print '\r{0}'.format(x),

En outre, cette approche ne nécessite pas la deuxième instruction d'impression.

David Marx
la source
Même cela ne fonctionne pas dans la console PyDev, pas plus qu'une virgule après la parenthèse fermée de print ().
Noumenon
n'a pas fonctionné pour moi, il écrit juste le résultat final (ex: "Progression du téléchargement à 100%") une fois le processus terminé.
Alexandre
2
@Alexandre Cela fonctionne très bien dans IPython ou son successeur Jupyter. Vous devez vider le tampon. stackoverflow.com/questions/17343688/…
mars Ho
7
for x in range(10):
    time.sleep(0.5) # shows how its working
    print("\r {}".format(x), end="")

time.sleep (0.5) est de montrer comment la sortie précédente est effacée et la nouvelle sortie est imprimée "\ r" quand elle est au début du message d'impression, elle va effacer la sortie précédente avant la nouvelle sortie.

Rubixred
la source
7

Cela fonctionne sur Windows et python 3.6

import time
for x in range(10):
    time.sleep(0.5)
    print(str(x)+'\r',end='')
Siddhant Sadangi
la source
5

La réponse acceptée n'est pas parfaite . La ligne qui a été imprimée en premier y restera et si votre deuxième impression ne couvre pas toute la nouvelle ligne, vous vous retrouverez avec du texte de garbage.

Pour illustrer le problème, enregistrez ce code en tant que script et exécutez-le (ou jetez-y simplement un œil):

import time

n = 100
for i in range(100):
    for j in range(100):
        print("Progress {:2.1%}".format(j / 100), end="\r")
        time.sleep(0.01)
    print("Progress {:2.1%}".format(i / 100))

La sortie ressemblera à ceci:

Progress 0.0%%
Progress 1.0%%
Progress 2.0%%
Progress 3.0%%

Ce qui fonctionne pour moi, c'est d'effacer la ligne avant de laisser une impression permanente. N'hésitez pas à vous adapter à votre problème spécifique:

import time

ERASE_LINE = '\x1b[2K' # erase line command
n = 100
for i in range(100):
    for j in range(100):
        print("Progress {:2.1%}".format(j / 100), end="\r")
        time.sleep(0.01)
    print(ERASE_LINE + "Progress {:2.1%}".format(i / 100)) # clear the line first

Et maintenant, il imprime comme prévu:

Progress 0.0%
Progress 1.0%
Progress 2.0%
Progress 3.0%
mimoralea
la source
5

Voici une version plus propre et plus "plug-and-play" de la réponse de @ Nagasaki45. Contrairement à beaucoup d'autres réponses ici, cela fonctionne correctement avec des chaînes de différentes longueurs. Il y parvient en effaçant la ligne avec autant d'espaces que la longueur de la dernière ligne imprimée. Fonctionnera également sous Windows.

def print_statusline(msg: str):
    last_msg_length = len(print_statusline.last_msg) if hasattr(print_statusline, 'last_msg') else 0
    print(' ' * last_msg_length, end='\r')
    print(msg, end='\r')
    sys.stdout.flush()  # Some say they needed this, I didn't.
    print_statusline.last_msg = msg

Usage

Utilisez-le simplement comme ceci:

for msg in ["Initializing...", "Initialization successful!"]:
    print_statusline(msg)
    time.sleep(1)

Ce petit test montre que les lignes sont correctement effacées, même pour des longueurs différentes:

for i in range(9, 0, -1):
    print_statusline("{}".format(i) * i)
    time.sleep(0.5)
erb
la source
5
C'est une excellente solution car elle nettoie élégamment la sortie précédente sur toute sa longueur sans simplement imprimer un nombre arbitraire d'espaces blancs (c'est ce à quoi j'avais eu recours auparavant!) Merci
Toby Petty
2

Je suis un peu surpris que personne n'utilise le caractère de retour arrière. En voici un qui l'utilise.

import sys
import time

secs = 1000

while True:
    time.sleep(1)  #wait for a full second to pass before assigning a second
    secs += 1  #acknowledge a second has passed

    sys.stdout.write(str(secs))

    for i in range(len(str(secs))):
        sys.stdout.write('\b')
David Philipe Gil
la source
assurez-vous de rincer après avoir utilisé cette méthode ou vous pourriez ne rien imprimer à l'écran. sys.stdout.flush()
Gabriel Fair
2

(Python3) C'est ce qui a fonctionné pour moi. Si vous utilisez simplement le \ 010, il laissera des caractères, alors je l'ai légèrement modifié pour m'assurer qu'il écrasait ce qui était là. Cela vous permet également d'avoir quelque chose avant le premier élément d'impression et de supprimer uniquement la longueur de l'élément.

print("Here are some strings: ", end="")
items = ["abcd", "abcdef", "defqrs", "lmnop", "xyz"]
for item in items:
    print(item, end="")
    for i in range(len(item)): # only moving back the length of the item
        print("\010 \010", end="") # the trick!
        time.sleep(0.2) # so you can see what it's doing
RenegadeJr
la source
2

Encore une réponse basée sur les réponses précédentes.

Contenu de pbar.py: import sys, shutil, datetime

last_line_is_progress_bar=False


def print2(print_string):
    global last_line_is_progress_bar
    if last_line_is_progress_bar:
        _delete_last_line()
        last_line_is_progress_bar=False
    print(print_string)


def _delete_last_line():
    sys.stdout.write('\b\b\r')
    sys.stdout.write(' '*shutil.get_terminal_size((80, 20)).columns)
    sys.stdout.write('\b\r')
    sys.stdout.flush()


def update_progress_bar(current, total):
    global last_line_is_progress_bar
    last_line_is_progress_bar=True

    completed_percentage = round(current / (total / 100))
    current_time=datetime.datetime.now().strftime('%m/%d/%Y-%H:%M:%S')
    overhead_length = len(current_time+str(current))+13
    console_width = shutil.get_terminal_size((80, 20)).columns - overhead_length
    completed_width = round(console_width * completed_percentage / 100)
    not_completed_width = console_width - completed_width
    sys.stdout.write('\b\b\r')

    sys.stdout.write('{}> [{}{}] {} - {}% '.format(current_time, '#'*completed_width, '-'*not_completed_width, current,
                                        completed_percentage),)
    sys.stdout.flush()

Utilisation du script:

import time
from pbar import update_progress_bar, print2


update_progress_bar(45,200)
time.sleep(1)

update_progress_bar(70,200)
time.sleep(1)

update_progress_bar(100,200)
time.sleep(1)


update_progress_bar(130,200)
time.sleep(1)

print2('some text that will re-place current progress bar')
time.sleep(1)

update_progress_bar(111,200)
time.sleep(1)

print('\n') # without \n next line will be attached to the end of the progress bar
print('built in print function that will push progress bar one line up')
time.sleep(1)

update_progress_bar(111,200)
time.sleep(1)
Krisz
la source
1

Voici ma solution! Windows 10, Python 3.7.1

Je ne sais pas pourquoi ce code fonctionne, mais il efface complètement la ligne d'origine. Je l'ai compilé à partir des réponses précédentes. Les autres réponses ne feraient que ramener la ligne au début, mais si vous aviez une ligne plus courte par la suite, elle aurait l'air foirée comme hellose transforme en byelo.

import sys
#include ctypes if you're on Windows
import ctypes
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
#end ctypes

def clearline(msg):
    CURSOR_UP_ONE = '\033[K'
    ERASE_LINE = '\x1b[2K'
    sys.stdout.write(CURSOR_UP_ONE)
    sys.stdout.write(ERASE_LINE+'\r')
    print(msg, end='\r')

#example
ig_usernames = ['beyonce','selenagomez']
for name in ig_usernames:
    clearline("SCRAPING COMPLETE: "+ name)

Sortie - Chaque ligne sera réécrite sans aucun ancien texte montrant:

SCRAPING COMPLETE: selenagomez

Ligne suivante (complètement réécrite sur la même ligne):

SCRAPING COMPLETE: beyonce
kpossibles
la source
0

Mieux vaut écraser toute la ligne sinon la nouvelle ligne se mélangera avec les anciennes si la nouvelle ligne est plus courte.

import time, os
for s in ['overwrite!', 'the!', 'whole!', 'line!']:
    print(s.ljust(os.get_terminal_size().columns - 1), end="\r")
    time.sleep(1)

Dû utiliser columns - 1sur Windows.

Carl Chang
la source