在同一行输出并覆盖先前的输出?

165

我正在编写一个FTP下载器。其中一部分代码类似于这样:

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

我正在调用process函数来处理回调:

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

输出结果大致如下:

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

但我希望它打印这行文字,下次重新打印/刷新时,只显示一次,并且我可以看到下载的进度。

如何做到这一点?


1
可能是控制台中的文本进度条的重复问题。 - Alexey
这与“控制台中的文本进度条”绝不是重复的。 这是一个类似的问题,但并不相同。 洗狗和洗猫很相似,它们都是哺乳动物。洗猫总是需要大量的锁子甲,而洗狗通常有8种不同的情况之一。 - Malcolm Anderson
1
尽管所有答案都会打印在同一行上,但如果后续行较短,则它们可能并不真正覆盖一行。为了确保这一点,应对所有要打印的变量使用字段宽度说明符。例如,如果使用f-strings,则可以使用以下代码:print(f'\r{filename:<30s} {progress<6d} downloaded', end='', flush=True) - farhanhubble
这个回答解决了你的问题吗?[动态地在一行中打印(https://dev59.com/I3A75IYBdhLWcg3wg5fs)] - mkrieger1
13个回答

299

这是Python 3.x的代码:

print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!', end='\r')
end= 关键字在这里起作用--默认情况下,print() 以换行符(\n)结束,但可以替换为其他字符串。在本例中,以回车符结束将使光标返回到当前行的开头。因此,在这种简单的用法中无需导入sys模块。实际上,print()有许多关键字参数可用于大大简化代码。此处有更多信息。
要在Python 2.6+上使用相同的代码,请在文件顶部添加以下行:
from __future__ import print_function

6
请注意,在Python 2.6+ 中,可以通过在文件顶部添加以下导入语句来使用print函数: from __future__ import print_function - janin
12
在Python 3.0之前,一条语句末尾的逗号可以防止输出换行符: print "foo", 但是你仍然需要在输出后刷新才能看到更改: sys.stdout.flush() - Tobias Domhan
53
我发现我需要在字符串开头包含\r,并将end=''设置为了使它工作。我认为我的终端不喜欢以\r结尾。 - Jezzamon
7
在所有当前的Python 3版本中,您可以在print()函数中添加flush=True来确保缓冲区被刷新(标准的行缓冲只有在写入\n换行符时才会刷新)。 - Martijn Pieters
8
在Jupyter Notebook中,使用\r放在开头并加上end=''效果最好且最顺畅。在字符串结尾处使用\r并加上flush=Trueend='\r'也可以运行,但是不太顺畅,并且会删除该行及其换行符。 - Dave X
显示剩余5条评论

47

如果你只想改变一行的内容,可以使用\r\r表示回车符。它的作用仅是将插入符放回当前行的开头,不会擦除任何内容。类似地,\b可用于向后移动一个字符位置。(某些终端可能不支持所有这些特性)

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)

1
我想补充一下,\r 表示回车符。它的作用仅仅是将光标放回当前行的开头,不会删除任何内容。同样地,\b 可以用于向后移动一个字符。(有些终端可能不支持所有这些功能) - Adrien Plisson
我认为你的意思是 sys.stdout.write('%s\r' % size_str),但它不起作用。 - Kristian
@Adrien:很酷,已添加。@Kristian:谢谢,已更新,当时已经很晚了。 - Sam Dolan
还要注意,如果你喜欢的话,仍然可以使用 print 而不是 sys.stdout.writeprint size_str + "\r", 可以正常工作。在 print 语句末尾的 , 抑制了换行符;但是 sys.stdout.flush() 仍然是必需的。 - Jeff

18

请查看curses模块文档curses模块HOWTO

以下为一个非常基础的示例:

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

11
不幸的是,Curses 只能在 Unix 上使用。该帖子的作者没有告诉我们他的应用程序针对哪个操作系统... - Adrien Plisson
我已经习惯在Linux下工作了,以至于我甚至没有注意到模块文档中关于该模块仅适用于UNIX的警告。感谢您指出这一点,@Adrien。 - Andrea Spadaccini
3
@AdrienPlisson,我知道这个问题是几年前提出的,但实际上你可以将Curses移植到Windows平台:http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses - Cold Diamondz
当我将这个粘贴到我的Python解释器中时,我的终端变得混乱了。退出解释器也没有帮助。什么鬼?! - allyourcode
使用clrtoeol来清除第二行比第一行短的情况。 - Serge Stroobandt
如果你正在使用Jupyter,这种方法可能会损坏你的内核。 - MattY

14

这是我的小型类,可以重新打印文本块。它会正确地清除先前的文本,因此您可以使用更短的新文本覆盖旧文本而不会弄乱。

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

3
这在Windows 10之前的版本中不起作用,因为Windows 10是第一个支持这些控制字符的Windows版本。参考链接:https://en.wikipedia.org/wiki/ANSI_escape_code - user136036
谢谢,这是一个很好的起点。我在Python 3.8中遇到了一些问题,所以写了一个新的修复了错误的实现,请看我的答案。 - kwiknik

13

我正在使用Spyder 3.3.1 - Windows 7 - Python 3.6,虽然可能不需要使用flush。

基于此帖子 - 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()

1
不需要刷新。我试过没有刷新,它完全可以工作。 - Donald Duck
这对我来说是关于Python 3.11在Windows 10上的总结:prinf('\r...', end=''),所以回车符在字符串开头而不是结尾。不需要使用flush,正如@DonaldDuck已经说过的那样。 - Flinsch

9

如果你想在Python中覆盖先前的行,只需在print函数中添加end='\r'即可。请尝试以下示例:

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

运行完美。谢谢。 - Talha Anwar

8

我发现在Python 2.7中,如果想要简单的打印输出语句,只需要在'\r'之后加上一个逗号就可以了。

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

这个方法比其他非Python 3的解决方案更短,但维护起来也更有难度。

1
Python 2.7.10,它没有打印任何东西。也许,您可以在线编译它并发送链接以查看它的工作情况...? - George Chalhoub
这个答案还需要 sys.stdout.flush() 来确保输出在屏幕上更新。 - BigBrownBear00

5

Python 3.9

for j in range(1,5):
    print('\rwaiting : '+str(j), end='')

5
您可以在字符串末尾添加'\r',并在print函数末尾加上逗号。例如:
print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!\r'),

1
这是针对Python 3的吗?如果是,那么默认的 end 参数设置为 \n,所以你可以打印出 (...)KB downloaded!\r\n。我错了吗? - S.R
1
使用 end = '\r' 可以在 Python 3 中解决这个问题。 - Abhimanyu Pallavi Sudhir

0

感谢Bouke Versteegh的回答,为我们提供了一个很好的起点。但是我发现它存在问题,即sys.stdout没有被正确刷新,并且重新打印的文本会一遍又一遍地向下移动。

我的解决方案是在该答案的基础上进行阐述,针对这些问题重新实现并添加了__call__方法以更方便地重新打印调用。

已在Python 3.8上进行了测试和工作。

import re, sys

class Reprinter:

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

    def write(self, text):
        """Writes text to stdout."""
        sys.stdout.write(text)
        sys.stdout.flush()

    def cursor_prev_line(self):
        """Moves cursor up a line."""
        self.write("\x1b[A")

    def cursor_prev_line_start(self):
        """Moves cursor to start of previous line."""
        self.write("\x1b[F")

    def cursor_line_start(self):
        """Moves cursor to start of current line."""
        self.write("\r")

    def cursor_text_start(self):
        """Moves cursor to start of current text."""
        num_newlines = self.text.count("\n")
        if not num_newlines:
            return self.cursor_line_start()
        for _ in range(num_newlines+1):
            self.cursor_prev_line_start()

    def erase(self):
        """Erases current text by replacing non-whitespace chars with spaces."""
        self.cursor_text_start()
        self.write(re.sub(r"[^\s]", " ", self.text))
        self.cursor_text_start()

    def __call__(self, text):
        """Prints `text` & overwrites all existing text."""
        self.erase()
        self.write(text)
        self.text = text

reprint = Reprinter()
reprint("How now\nbrown cow")
reprint("The rain\nin Spain")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接