Output to the same line overwriting previous output?
Question:
I am writing an FTP downloader. Part of to the code is something like this:
ftp.retrbinary("RETR " + file_name, process)
I am calling function process to handle the callback:
def process(data):
print os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!'
file.write(data)
and output is something like this:
1784 KB / KB 1829 downloaded!
1788 KB / KB 1829 downloaded!
etc...
but I want it to print this line and next time reprint/refresh it so it will only show it once and I will see progress of that download.
How can it be done?
Answers:
Have a look at the curses module documentation and the curses module HOWTO.
Really basic example:
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()
If all you want to do is change a single line, use r
. r
means carriage return. It’s effect is solely to put the caret back at the start of the current line. It does not erase anything. Similarly, b
can be used to go one character backward. (some terminals may not support all those features)
import sys
def process(data):
size_str = os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!'
sys.stdout.write('%sr' % size_str)
sys.stdout.flush()
file.write(data)
Here’s code for Python 3.x:
print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!', end='r')
The end=
keyword is what does the work here — by default, print()
ends in a newline (n
) character, but this can be replaced with a different string. In this case, ending the line with a carriage return instead returns the cursor to the start of the current line. Thus, there’s no need to import the sys
module for this sort of simple usage. print()
actually has a number of keyword arguments which can be used to greatly simplify code.
To use the same code on Python 2.6+, put the following line at the top of the file:
from __future__ import print_function
Here’s my little class that can reprint blocks of text. It properly clears the previous text so you can overwrite your old text with shorter new text without creating a mess.
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("FoobarnBazbar")
reprinter.reprint("Foonbar")
I found that for a simple print statement in python 2.7, just put a comma at the end after your 'r'
.
print os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!r',
This is shorter than other non-python 3 solutions, but also more difficult to maintain.
You can just add ‘r’ at the end of the string plus a comma at the end of print function. For example:
print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!r'),
I am using spyder 3.3.1 – windows 7 – python 3.6
although flush may not be needed.
based on this posting – 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()
to overwiting the previous line in python all wath you need is to add end=’r’ to the print function, test this example:
import time
for j in range(1,5):
print('waiting : '+j, end='r')
time.sleep(1)
Python 3.9
for j in range(1,5):
print('rwaiting : '+str(j), end='')
This is my asnwer. I have search for that too. I got many solution but this works for me.
from time import sleep
for _ in range(5):
sleep(1)
print(f'rWritting on same line with chaning number {_}.',end='')
else:
print()
- Yes you can use
else:
statement with for
block. That will add new line when for
loop finish.
- This will only work with loops. It will not affect other
print()
statements.
Thanks to Bouke Versteegh for their answer which gives a great starting point. However I found issues with it where sys.stdout
wasn’t flushed as it should be, and reprinted text moved down the screen repeatedly.
My solution here is an elaboration on that answer, reimplemented to fix those issues and with a __call__
method for more convenient reprint invocation.
Tested & working on 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 nownbrown cow")
reprint("The rainnin Spain")
I am writing an FTP downloader. Part of to the code is something like this:
ftp.retrbinary("RETR " + file_name, process)
I am calling function process to handle the callback:
def process(data):
print os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!'
file.write(data)
and output is something like this:
1784 KB / KB 1829 downloaded!
1788 KB / KB 1829 downloaded!
etc...
but I want it to print this line and next time reprint/refresh it so it will only show it once and I will see progress of that download.
How can it be done?
Have a look at the curses module documentation and the curses module HOWTO.
Really basic example:
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()
If all you want to do is change a single line, use r
. r
means carriage return. It’s effect is solely to put the caret back at the start of the current line. It does not erase anything. Similarly, b
can be used to go one character backward. (some terminals may not support all those features)
import sys
def process(data):
size_str = os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!'
sys.stdout.write('%sr' % size_str)
sys.stdout.flush()
file.write(data)
Here’s code for Python 3.x:
print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!', end='r')
The end=
keyword is what does the work here — by default, print()
ends in a newline (n
) character, but this can be replaced with a different string. In this case, ending the line with a carriage return instead returns the cursor to the start of the current line. Thus, there’s no need to import the sys
module for this sort of simple usage. print()
actually has a number of keyword arguments which can be used to greatly simplify code.
To use the same code on Python 2.6+, put the following line at the top of the file:
from __future__ import print_function
Here’s my little class that can reprint blocks of text. It properly clears the previous text so you can overwrite your old text with shorter new text without creating a mess.
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("FoobarnBazbar")
reprinter.reprint("Foonbar")
I found that for a simple print statement in python 2.7, just put a comma at the end after your 'r'
.
print os.path.getsize(file_name)/1024, 'KB / ', size, 'KB downloaded!r',
This is shorter than other non-python 3 solutions, but also more difficult to maintain.
You can just add ‘r’ at the end of the string plus a comma at the end of print function. For example:
print(os.path.getsize(file_name)/1024+'KB / '+size+' KB downloaded!r'),
I am using spyder 3.3.1 – windows 7 – python 3.6
although flush may not be needed.
based on this posting – 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()
to overwiting the previous line in python all wath you need is to add end=’r’ to the print function, test this example:
import time
for j in range(1,5):
print('waiting : '+j, end='r')
time.sleep(1)
Python 3.9
for j in range(1,5):
print('rwaiting : '+str(j), end='')
This is my asnwer. I have search for that too. I got many solution but this works for me.
from time import sleep
for _ in range(5):
sleep(1)
print(f'rWritting on same line with chaning number {_}.',end='')
else:
print()
- Yes you can use
else:
statement withfor
block. That will add new line whenfor
loop finish. - This will only work with loops. It will not affect other
print()
statements.
Thanks to Bouke Versteegh for their answer which gives a great starting point. However I found issues with it where sys.stdout
wasn’t flushed as it should be, and reprinted text moved down the screen repeatedly.
My solution here is an elaboration on that answer, reimplemented to fix those issues and with a __call__
method for more convenient reprint invocation.
Tested & working on 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 nownbrown cow")
reprint("The rainnin Spain")