Come sovrascrivere la stampa precedente su stdout in Python?


113

Se avessi il seguente codice:

for x in range(10):
     print x

Otterrei l'output di

1
2
etc..

Quello che vorrei fare è invece di stampare una nuova riga, voglio sostituire il valore precedente e sovrascriverlo con il nuovo valore sulla stessa riga.



Come nota a margine, assicurati di ottenere la risposta che è in grado di svuotare l'intera linea se la linea precedente è più lunga della linea corrente.
Frutta

Risposte:


119

Un modo è usare il '\r'carattere di ritorno a capo ( ) per tornare all'inizio della riga senza passare alla riga successiva:

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

La virgola alla fine dell'istruzione print dice di non andare alla riga successiva. L'ultima istruzione di stampa avanza alla riga successiva in modo che il prompt non sovrascriva l'output finale.

Aggiornare

Ora che Python 2 è EOL, una risposta di Python 3 ha più senso. Per Python 3.5 e versioni precedenti:

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

In Python 3.6 e versioni successive, le stringhe f leggono meglio:

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

Funziona, ma non capisco un paio di parti ... "{0}" e
.format

7
Questo è introdotto in Python 2.6 e (mi è stato detto) è il modo standard di formattare le stringhe in Python 3; l' %operatore è deprecato. Fondamentalmente, tutte le stringhe ora hanno un metodo di formattazione . In questo caso, {0}significa "il primo argomento a format()" (il conteggio inizia da 0).
Mike DeSimone

Non è un'opzione se devi utilizzare alcuni software solo per Windows e non hai le braciole per eseguire una VM.
Mike DeSimone

2
E se vuoi essere cross-terminal, re.sub(r'\$<\d+>[/*]?', '', curses.tigetstr('el') or '')ti darà la stringa di cancellazione fino alla fine della riga corretta per qualunque terminale l'utente abbia. Ricordarsi di inizializzare cursescon qualcosa di simile curses.setupterm(fd=sys.stdout.fileno())e utilizzare sys.stdout.isatty()per assicurarsi che l'output non venga reindirizzato a un file. Vedi code.activestate.com/recipes/475116 per un modulo Python completo con controllo del cursore e supporto del colore.
Mike DeSimone

1
@PhilMacKay: l'ho provato in Python sul mio Mac: In [3]: print "Thing to erase \ r",; time.sleep (1); print "-------------- \ r", -------------- Penso che il tuo problema sia Windows e le sue diverse estremità di riga. Prova questo: import curses; curses.setupterm(fd=sys.stdout.fileno()); print(hex(curses.tigetstr('cr')));dovrebbe stampare i codici esadecimali della sequenza di caratteri per andare all'inizio della riga.
Mike DeSimone

107

Dato che sono finito qui tramite Google ma sto usando Python 3, ecco come funzionerebbe in Python 3:

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

Risposta correlata qui: come posso sopprimere la nuova riga dopo un'istruzione di stampa?


3
Ho provato questo e per lo più funziona bene. L'unico problema è che non sta cancellando l'output precedente su quella riga, ad esempio: se il tuo primo ciclo stampato testinge il tuo secondo ciclo stampa testl'output dopo il secondo passaggio sarà ancora testing- ora vedo che @ Nagasaki45 lo ha sottolineato
Eric Uldall

15
in un notebook ipython, devo fare:print("\r", message, end="")
grisaitis

1
Funziona alla grande, non cancella la riga, ma l'uso di spazi bianchi sufficienti nella stampa successiva fa il lavoro (sebbene non sia elegante). Un commento che potrei aggiungere è che questo non funziona nella console di debug di VS Code, ma funziona nel terminale.
PhilMacKay

31

La risposta di @Mike DeSimone probabilmente funzionerà per la maggior parte del tempo. Ma...

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

-> 1bc

Questo perché l' '\r'unico torna all'inizio della riga ma non cancella l'output.

EDIT: soluzione migliore (rispetto alla mia vecchia proposta di seguito)

Se il supporto POSIX è sufficiente per te, quanto segue cancellerebbe la riga corrente e lascerebbe il cursore all'inizio:

print '\x1b[2K\r',

Utilizza il codice di escape ANSI per cancellare la riga del terminale. Maggiori informazioni possono essere trovate in wikipedia e in questo grande discorso .

Vecchia risposta

La soluzione (non così buona) che ho trovato assomiglia a questa:

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

-> 1

Un vantaggio è che funzionerà anche su Windows.


1
Ho creato una funzione dalla 'vecchia risposta' che funziona correttamente (anche su Windows), vedi la mia risposta: stackoverflow.com/a/43952192/965332
erb

25

Ho avuto la stessa domanda prima di visitare questo thread. Per me sys.stdout.write ha funzionato solo se ho svuotato correttamente il buffer, ad es

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

Senza flush, il risultato viene stampato solo alla fine dello script


Ottima anche l'idea di riempire il resto della stringa con spazi come stringvar.ljust(10,' ')nel caso di stringhe di lunghezza variabile.
Annarfych

@chris, Don et al. questo è Python 2, la maggior parte delle altre risposte sono Python 3. Tutti dovrebbero usare Python 3 ora; Python 2 terminerà nel 2020.
smci

18

Elimina la nuova riga e stampa \r.

print 1,
print '\r2'

o scrivi a stdout:

sys.stdout.write('1')
sys.stdout.write('\r2')

9

Prova questo:

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

Ha funzionato per me. La end="\r"parte sta facendo in modo che sovrascriva la riga precedente.

AVVERTIMENTO!

Se stampi hi, quindi stampi hellousando \r, otterrai hilloperché l'output ha scritto sulle due lettere precedenti. Se stampi hicon spazi (che non vengono visualizzati qui), verrà stampato hi. Per risolvere questo problema, stampa gli spazi usando \r.


1
Questo non funziona per me. Ho copiato quel codice, ma il mio output è Hi BobHi BobHi BobHi BobHi Bob. Python 3.7.1
PaulMag

1
Ma la risposta di Rubixred ha funzionato. Strano. A me sembrano lo stesso metodo. L'unica differenza che vedo è che \rè all'inizio invece che alla fine.
PaulMag

7

Non sono riuscito a far funzionare nessuna delle soluzioni in questa pagina per IPython , ma una leggera variazione sulla soluzione di @ Mike-Desimone ha funzionato: invece di terminare la riga con il ritorno a capo, inizia la riga con il ritorno a capo:

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

Inoltre, questo approccio non richiede la seconda istruzione print.


Anche questo non funziona nella console PyDev, né una virgola dopo la parentesi chiusa di print ().
Noumenon

non ha funzionato per me, scrive solo il risultato finale (es: "Download avanzamento 100%") una volta terminato il processo.
Alexandre

2
@Alexandre Funziona bene in IPython o nel suo successore Jupyter. Devi svuotare il buffer. stackoverflow.com/questions/17343688/…
March Ho

7
for x in range(10):
    time.sleep(0.5) # shows how its working
    print("\r {}".format(x), end="")

time.sleep (0.5) mostra come l'output precedente viene cancellato e il nuovo output viene stampato "\ r" quando è all'inizio del messaggio di stampa, cancellerà l'output precedente prima del nuovo output.


7

Funziona su Windows e Python 3.6

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

5

La risposta accettata non è perfetta . La riga che è stata stampata per prima rimarrà lì e se la seconda stampa non copre l'intera nuova riga, finirai con il testo spazzatura.

Per illustrare il problema, salva questo codice come script ed eseguilo (o dai un'occhiata):

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

L'output sarà simile a questo:

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

Quello che funziona per me è cancellare la linea prima di lasciare una stampa permanente. Sentiti libero di adattarti al tuo problema specifico:

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

E ora stampa come previsto:

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

5

Ecco una versione più pulita, più "plug-and-play" della risposta di @ Nagasaki45. A differenza di molte altre risposte qui, funziona correttamente con stringhe di diverse lunghezze. Ottiene ciò cancellando la linea con tanti spazi quanti la lunghezza dell'ultima riga stampata. Funzionerà anche su 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

uso

Usalo semplicemente in questo modo:

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

Questo piccolo test mostra che le linee vengono cancellate correttamente, anche per lunghezze diverse:

for i in range(9, 0, -1):
    print_statusline("{}".format(i) * i)
    time.sleep(0.5)

5
Questa è un'ottima soluzione in quanto pulisce elegantemente l'output precedente per tutta la sua lunghezza senza solo stampare un numero arbitrario di spazi bianchi (che è quello a cui avevo fatto ricorso in precedenza!) Grazie
Toby Petty

2

Sono un po 'sorpreso che nessuno stia usando il carattere backspace. Eccone uno che lo usa.

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

assicurati di lavare dopo aver usato questo metodo o potresti non ottenere nulla stampato sullo schermo. sys.stdout.flush()
Gabriel Fair

2

(Python3) Questo è ciò che ha funzionato per me. Se usi solo \ 010, lascerà i caratteri, quindi l'ho modificato un po 'per assicurarmi che sovrascriva ciò che era lì. Ciò consente anche di avere qualcosa prima del primo elemento di stampa e di rimuovere solo la lunghezza dell'articolo.

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

2

Un'altra risposta basata sulle risposte precedenti.

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

Utilizzo dello 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)

1

Ecco la mia soluzione! Windows 10, Python 3.7.1

Non sono sicuro del motivo per cui questo codice funziona, ma cancella completamente la riga originale. L'ho compilato dalle risposte precedenti. Le altre risposte riporterebbero semplicemente la linea all'inizio, ma se avessi una linea più corta dopo, sembrerebbe incasinata come hellosi trasforma in 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)

Output: ogni riga verrà riscritta senza che il vecchio testo mostri:

SCRAPING COMPLETE: selenagomez

Riga successiva (riscritta completamente sulla stessa riga):

SCRAPING COMPLETE: beyonce

0

Meglio sovrascrivere l'intera riga altrimenti la nuova riga si mescolerà con quelle vecchie se la nuova riga è più corta.

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

Ho dovuto usare columns - 1su Windows.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.