Como substituir a impressão anterior para stdout em python?

113

Se eu tivesse o seguinte código:

for x in range(10):
     print x

Eu obteria a saída de

1
2
etc..

O que eu gostaria de fazer é, em vez de imprimir uma nova linha, quero substituir o valor anterior e substituí-lo pelo novo valor na mesma linha.

ccwhite1
fonte
possível duplicata de Python - Remover e substituir itens impressos
Sven Marnach
Como uma observação lateral, certifique-se de obter a resposta capaz de liberar toda a linha se a linha anterior for mais longa do que a atual.
Frutas

Respostas:

119

Uma maneira é usar o '\r'caractere de retorno de carro ( ) para retornar ao início da linha sem avançar para a próxima linha:

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

A vírgula no final da instrução de impressão diz para não ir para a próxima linha. A última instrução de impressão avança para a próxima linha para que seu prompt não substitua sua saída final.

Atualizar

Agora que Python 2 é EOL, uma resposta de Python 3 faz mais sentido. Para Python 3.5 e anterior:

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

No Python 3.6 e posterior, as strings f são lidas melhor:

for x in range(10):
    print(f'{x}\r', end="")
print()
Mike DeSimone
fonte
Isso funciona, mas não entendi algumas das partes ... '{0}' e
.format
7
Isso é apresentado no Python 2.6 e (segundo me disseram) é a maneira padrão de formatar strings no Python 3; o %operador está obsoleto. Basicamente, todas as strings agora têm um método de formatação . Nesse caso, {0}significa "o primeiro argumento para format()" (a contagem começa em 0).
Mike DeSimone
Não é uma opção se você tiver que usar algum software somente para Windows e não tiver condições de executar uma VM.
Mike DeSimone
2
E se você quiser ser um terminal cruzado, re.sub(r'\$<\d+>[/*]?', '', curses.tigetstr('el') or '')fornecerá a seqüência correta de apagar até o fim da linha para qualquer terminal que o usuário tenha. Lembre-se de inicializar cursescom algo como curses.setupterm(fd=sys.stdout.fileno())e use sys.stdout.isatty()para ter certeza de que sua saída não está sendo redirecionada para um arquivo. Consulte code.activestate.com/recipes/475116 para um módulo Python completo com controle de cursor e suporte a cores.
Mike DeSimone
1
@PhilMacKay: Eu tentei isso em python no meu Mac: Em [3]: print "Thing to erase \ r",; tempo.sono (1); print "-------------- \ r", -------------- Acho que seu problema é o Windows e suas diferentes extremidades de linha. Tente isto: import curses; curses.setupterm(fd=sys.stdout.fileno()); print(hex(curses.tigetstr('cr')));deve imprimir os códigos hexadecimais da seqüência de caracteres para ir para o início da linha.
Mike DeSimone
107

Como acabei aqui por meio do Google, mas estou usando Python 3, é assim que funcionaria em Python 3:

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

Resposta relacionada aqui: Como posso suprimir a nova linha após uma declaração de impressão?

Pascal
fonte
3
Eu tentei isso e quase sempre funciona bem. O único problema é que ele não está apagando a saída anterior nessa linha, por exemplo: se sua primeira impressão de loop testinge sua segunda impressão de loop, testa saída após a segunda passagem ainda será testing- agora vejo que @ Nagasaki45 apontou isso
Eric Uldall
15
em um notebook ipython, eu tenho que fazer:print("\r", message, end="")
grisaitis
1
Isso funciona muito bem, não apaga a linha, mas usar espaços em branco suficientes na impressão a seguir faz o trabalho (embora não seja elegante). Um comentário que devo acrescentar é que isso não funciona no console de depuração do VS Code, mas funciona no terminal.
PhilMacKay
31

A resposta de @Mike DeSimone provavelmente funcionará na maioria das vezes. Mas...

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

-> 1bc

Isso ocorre porque o '\r'único volta para o início da linha, mas não limpa a saída.

EDIT: Melhor solução (do que minha antiga proposta abaixo)

Se o suporte POSIX for suficiente para você, o seguinte limparia a linha atual e deixaria o cursor em seu início:

print '\x1b[2K\r',

Ele usa o código de escape ANSI para limpar a linha do terminal. Mais informações podem ser encontradas na wikipedia e nesta ótima palestra .

Resposta antiga

A solução (não tão boa) que encontrei se parece com esta:

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

-> 1

Uma vantagem é que também funciona no Windows.

Nagasaki45
fonte
1
Eu criei uma função da 'resposta antiga' que funciona corretamente (mesmo no Windows), veja minha resposta: stackoverflow.com/a/43952192/965332
erb
25

Eu tinha a mesma pergunta antes de visitar este tópico. Para mim, o sys.stdout.write funcionou apenas se eu limpar o buffer corretamente, ou seja,

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

Sem flushing, o resultado é impresso apenas no final do script

user2793078
fonte
Também é boa ideia preencher o resto da string com espaços, como stringvar.ljust(10,' ')no caso de strings com comprimento variável.
Annarfych
@chris, Don et al. este é Python 2, a maioria das outras respostas são Python 3. Todos deveriam estar usando Python 3 agora; Python 2 é fim de vida em 2020.
smci
18

Suprima a nova linha e imprima \r.

print 1,
print '\r2'

ou escreva para stdout:

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

Experimente isto:

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

Funcionou para mim A end="\r"parte está fazendo com que sobrescreva a linha anterior.

AVISO!

Se você imprimir e hi, em seguida, imprimir hellousando \r, você obterá hilloporque a saída escreveu sobre as duas letras anteriores. Se você imprimir hicom espaços (que não aparecem aqui), ele será impresso hi. Para corrigir isso, imprima os espaços usando \r.

Sam Bernstein
fonte
1
Isso não funciona para mim. Eu copiei esse código, mas minha saída é Hi BobHi BobHi BobHi BobHi Bob. Python 3.7.1
PaulMag
1
Mas a resposta de Rubixred funcionou. Esquisito. Eles parecem o mesmo método para mim. A única diferença que vejo é que \restá no início, e não no fim.
PaulMag de
7

Não consegui fazer com que nenhuma das soluções desta página funcionasse para IPython , mas uma ligeira variação na solução de @Mike-Desimone resolveu: em vez de terminar a linha com o retorno de carro, comece a linha com o retorno de carro:

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

Além disso, essa abordagem não requer a segunda instrução de impressão.

David marx
fonte
Mesmo isso não funciona no console PyDev, nem uma vírgula após o parêntese de fechamento de print ().
Noumenon
não funcionou para mim, ele apenas grava o resultado final (ex: "Download progress 100%") assim que o processo é concluído.
Alexandre
2
@Alexandre Funciona bem no IPython ou em seu sucessor Jupyter. Você precisa liberar o buffer. stackoverflow.com/questions/17343688/…
Março Ho,
7
for x in range(10):
    time.sleep(0.5) # shows how its working
    print("\r {}".format(x), end="")

time.sleep (0,5) é para mostrar como a saída anterior é apagada e a nova saída é impressa "\ r" quando está no início da mensagem de impressão, ela irá apagar a saída anterior antes da nova saída.

Rubixred
fonte
7

Isso funciona no Windows e python 3.6

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

A resposta aceita não é perfeita . A linha que foi impressa primeiro permanecerá lá e se a segunda impressão não cobrir toda a nova linha, você acabará com o texto lixo.

Para ilustrar o problema, salve este código como um script e execute-o (ou apenas dê uma olhada):

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

A saída será semelhante a esta:

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

O que funciona para mim é limpar a linha antes de deixar uma impressão permanente. Sinta-se à vontade para se ajustar ao seu problema específico:

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 agora ele imprime conforme o esperado:

Progress 0.0%
Progress 1.0%
Progress 2.0%
Progress 3.0%
mimoralea
fonte
5

Aqui está uma versão mais limpa e "plug-and-play" da resposta de @ Nagasaki45. Ao contrário de muitas outras respostas aqui, ele funciona corretamente com strings de comprimentos diferentes. Ele consegue isso limpando a linha com tantos espaços quanto o comprimento da impressão da última linha. Também funcionará no 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

Basta usá-lo assim:

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

Este pequeno teste mostra que as linhas são eliminadas corretamente, mesmo para comprimentos diferentes:

for i in range(9, 0, -1):
    print_statusline("{}".format(i) * i)
    time.sleep(0.5)
erb
fonte
5
Esta é uma ótima solução, pois limpa elegantemente a saída anterior em seu comprimento total, sem apenas imprimir um número arbitrário de espaços em branco (que é o que eu havia recorrido anteriormente!) Obrigado
Toby Petty
2

Estou um pouco surpreso por ninguém estar usando o caractere de retrocesso. Aqui está um que o 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')
David Philipe Gil
fonte
certifique-se de limpar depois de usar este método ou você não obterá nada impresso na tela. sys.stdout.flush()
Gabriel Fair
2

(Python3) Isso é o que funcionou para mim. Se você apenas usar o \ 010, ele deixará caracteres, então eu ajustei um pouco para ter certeza de que está substituindo o que estava lá. Isso também permite que você tenha algo antes do primeiro item de impressão e apenas remova o comprimento do item.

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
fonte
2

Mais uma resposta com base nas respostas anteriores.

Conteúdo 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()

Uso do 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
fonte
1

Aqui está minha solução! Windows 10, Python 3.7.1

Não sei por que esse código funciona, mas apaga completamente a linha original. Compilei com base nas respostas anteriores. As outras respostas apenas retornariam a linha ao início, mas se você tivesse uma linha mais curta depois, pareceria bagunçada como se hellotransforma em 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)

Saída - Cada linha será reescrita sem qualquer texto antigo mostrando:

SCRAPING COMPLETE: selenagomez

Próxima linha (completamente reescrita na mesma linha):

SCRAPING COMPLETE: beyonce
possíveis
fonte
0

Melhor substituir toda a linha, caso contrário, a nova linha se misturará com as antigas se a nova linha for mais curta.

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

Tive que usar columns - 1no Windows.

Carl Chang
fonte