Saída para a mesma linha sobrescrevendo a saída anterior?

95

Estou escrevendo um downloader FTP. Parte do código é algo assim:

ftp.retrbinary("RETR " + file_name, process)

Estou chamando o processo de função para lidar com o retorno de chamada:

def process(data):
    print os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!'
    file.write(data)

e a saída é algo assim:

1784  KB / KB 1829 downloaded!
1788  KB / KB 1829 downloaded!
etc...   

mas eu quero imprimir esta linha e da próxima vez reimprimir / atualizar para que ela mostre apenas uma vez e eu verei o progresso desse download.

Como pode ser feito?

Kristian
fonte
Duplicado da barra de progresso de texto no console .
Alexey
Possível duplicata da barra de progresso
Alexey

Respostas:

185

Este é o código para Python 3.x:

print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!', end='\r')

A end=palavra-chave é o que faz o trabalho aqui - por padrão, print()termina em um \ncaractere de nova linha ( ), mas pode ser substituído por uma string diferente. Nesse caso, terminar a linha com um retorno de carro, em vez disso, retorna o cursor para o início da linha atual. Portanto, não há necessidade de importar o sysmódulo para esse tipo de uso simples. print()na verdade, tem vários argumentos de palavra-chave que podem ser usados ​​para simplificar bastante o código.

Para usar o mesmo código no Python 2.6+, coloque a seguinte linha na parte superior do arquivo:

from __future__ import print_function
Kudzu
fonte
6
Note-se que a função de impressão também pode ser usado em Python 2.6+, adicionando o seguinte importação no topo do arquivo: from __future__ import print_function.
janin
12
em python <3.0, uma vírgula no final da instrução impedirá um "\ n": print "foo",No entanto, você ainda precisa limpar depois disso para ver a mudança:sys.stdout.flush()
Tobias Domhan
30
Descobri que precisava incluir o \rno início da string e definir, em end=''vez disso, para fazer isso funcionar. Eu não acho que meu terminal goste quando eu terminar com\r
Jezzamon
2
Em todas as versões atuais do Python 3, você pode adicionar flush=Trueà print()função para garantir que o buffer seja liberado (o buffer de linha padrão só seria liberado quando uma \nnova linha fosse escrita).
Martijn Pieters
4
Em um notebook Jupyter, usando o \rno início e end=''funcionou melhor e mais suave. Usar flush=Truecom \rno final se a string e end='\r'também funcionou, mas não foi suave e apagou a linha e seu avanço de linha.
Dave X
40

Se tudo o que você deseja fazer é alterar uma única linha, use \r. \rsignifica retorno de carro. Seu efeito é apenas colocar o cursor de volta no início da linha atual. Não apaga nada. Da mesma forma, \bpode ser usado para retroceder um caractere. (alguns terminais podem não suportar todos esses recursos)

import sys

def process(data):
    size_str = os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!'
    sys.stdout.write('%s\r' % size_str)
    sys.stdout.flush()
    file.write(data)
Sam Dolan
fonte
1
Eu acrescentaria que \rsignifica retorno de carro. seu efeito é apenas colocar o cursor de volta no início da linha atual. não apaga nada. da mesma forma, \bpode ser usado para retroceder um caractere. (alguns terminais podem não oferecer suporte a todos esses recursos)
Adrien Plisson
sys.stdout.write('%s\r', size_str')eu acho que você quis dizer, sys.stdout.write('%s\r' % size_str)mas não funciona.
Kristian
@Adrien: Legal, adicionado isso. @Kristian: Obrigado, atualizei isso, já era muito tarde.
Sam Dolan
Observe também que você ainda pode usar em printvez de sys.stdout.writese preferir: print size_str + "\r",funciona bem. O ,no final da instrução print suprime a nova linha; sys.stdout.flush()ainda é necessário
Jeff
19

Dê uma olhada na documentação do módulo curses e no HOWTO do módulo curses .

Exemplo realmente básico:

import time
import curses

stdscr = curses.initscr()

stdscr.addstr(0, 0, "Hello")
stdscr.refresh()

time.sleep(1)

stdscr.addstr(0, 0, "World! (with curses)")
stdscr.refresh()
Andrea Spadaccini
fonte
10
infelizmente, curses só está disponível no Unix. o OP não nos disse qual sistema operacional seu aplicativo se destina ...
Adrien Plisson
Estou tão acostumado a trabalhar no Linux que nem percebi o aviso sobre o módulo ser apenas para UNIX na documentação do módulo. Obrigado por apontar isso, @Adrien.
Andrea Spadaccini
3
@AdrienPlisson, eu sei que essa pergunta foi feita anos atrás, mas você pode colocar as maldições no Windows: lfd.uci.edu/~gohlke/pythonlibs/#curses
Cold Diamondz
Quando colei isso em meu interpretador Python, meu terminal se tornou fubar. Sair do intérprete não ajudou. WTF ?!
código allyour
Use clrtoeolpara quando a segunda linha for mais curta que a primeira.
Serge Stroobandt
9

Aqui está minha pequena classe que pode reimprimir blocos de texto. Ele limpa corretamente o texto anterior para que você possa substituir o texto antigo por um novo texto mais curto, sem criar confusão.

import re, sys

class Reprinter:
    def __init__(self):
        self.text = ''

    def moveup(self, lines):
        for _ in range(lines):
            sys.stdout.write("\x1b[A")

    def reprint(self, text):
        # Clear previous text by overwritig non-spaces with spaces
        self.moveup(self.text.count("\n"))
        sys.stdout.write(re.sub(r"[^\s]", " ", self.text))

        # Print new text
        lines = min(self.text.count("\n"), text.count("\n"))
        self.moveup(lines)
        sys.stdout.write(text)
        self.text = text

reprinter = Reprinter()

reprinter.reprint("Foobar\nBazbar")
reprinter.reprint("Foo\nbar")
Bouke Versteegh
fonte
2
Isso não funcionará no Windows até o Windows 10, porque o Windows 10 é a primeira janela a oferecer suporte a esses caracteres de controle en.wikipedia.org/wiki/ANSI_escape_code
user136036,
8

Descobri que, para uma instrução de impressão simples no python 2.7, basta colocar uma vírgula no final após o seu '\r'.

print os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!\r',

Isso é mais curto do que outras soluções não-python 3, mas também mais difícil de manter.

Matt Ellen
fonte
1
Python 2.7.10, e não está imprimindo nada. Talvez você possa compilá-lo online e nos enviar um link para ver como está funcionando ...?
Shougo Makishima
5

Você pode simplesmente adicionar '\ r' no final da string mais uma vírgula no final da função de impressão. Por exemplo:

print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!\r'),
Moustafa Saleh
fonte
1
Isso é para python 3? Em caso afirmativo, com o endparâmetro padrão definido para \nso u print (...)KB downloaded!\r\n. Estou errado?
SR
Em end = '\r'vez disso, o uso corrige o problema no Python 3.
Abhimanyu Pallavi Sudhir
4

Estou usando o spyder 3.3.1 - windows 7 - python 3.6, embora o flush possa não ser necessário. com base nesta postagem - https://github.com/spyder-ide/spyder/issues/3437

   #works in spyder ipython console - \r at start of string , end=""
import time
import sys
    for i in range(20):
        time.sleep(0.5)
        print(f"\rnumber{i}",end="")
        sys.stdout.flush()
JoePythonKing
fonte
1

para substituir a linha anterior em python, tudo o que você precisa é adicionar end = '\ r' à função de impressão, teste este exemplo:

import time
for j in range(1,5):
   print('waiting : '+j, end='\r')
   time.sleep(1)
Amirouche Zeggagh
fonte