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.



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

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?


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.


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


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

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.


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.


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.


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

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%

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)

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

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

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)

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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.