115

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?

jonrsharpe
  • 99,167
  • 19
  • 183
  • 334
Kristian
  • 1,291
  • 2
  • 10
  • 9
  • Duplicate of [*Text progress bar in the console*](https://stackoverflow.com/q/3173320). – Alexey Mar 12 '19 at 08:30
  • Possible duplicate of [Text Progress Bar in the Console](https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console) – Alexey Mar 12 '19 at 08:32

8 Answers8

221

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
tshepang
  • 10,772
  • 21
  • 84
  • 127
Kudzu
  • 2,560
  • 1
  • 14
  • 16
  • 6
    Note that the print function can also be used in python 2.6+ by adding the following import at the top of the file : `from __future__ import print_function`. – janin Jul 11 '12 at 23:07
  • 12
    in python < 3.0 a comma at the end of the statement will prevent a "\n": `print "foo",` However you still need to flush after that to see the change: `sys.stdout.flush()` – Tobias Domhan Jul 24 '13 at 21:52
  • 36
    I found I needed to include the `\r` at the start of the string, and set `end=''` instead to get this to work. I don't think my terminal likes it when I end with `\r` – Jezzamon Jan 20 '16 at 00:46
  • 6
    In all current Python 3 releases, you can add `flush=True` to the `print()` function to ensure the buffer is flushed (the standard line buffer only would flush when a `\n` newline is written). – Martijn Pieters Nov 03 '16 at 12:54
  • 5
    In a jupyter notebook, using the `\r` at the beginning and `end=''` worked best and smoothest. Using `flush=True` with `\r` at the end if the string and `end='\r'` also worked but was not smooth & erased the line& its linefeed. – Dave X Oct 11 '17 at 19:08
  • @Kudzu: What if instead of one print, I have several prints and I want all of them to act this way?. If I change all print() this way, one will over write all others (They are in a loop, one in the outer loop and two in an inner loop) – Rika Mar 28 '18 at 12:19
  • Note that this will not work correctly with shells like IDLE and Spyder, but using `sys.stdout` will. – rovyko Sep 01 '18 at 22:02
43

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('%s\r' % size_str)
    sys.stdout.flush()
    file.write(data)
Jeff
  • 11,233
  • 10
  • 44
  • 85
Sam Dolan
  • 29,862
  • 8
  • 82
  • 84
  • 1
    i would add that `\r` means carriage return. its 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) – Adrien Plisson Feb 04 '11 at 11:26
  • `sys.stdout.write('%s\r', size_str')` i think you meant `sys.stdout.write('%s\r' % size_str)` but it doesnt work. – Kristian Feb 04 '11 at 11:43
  • @Adrien: Cool, added that. @Kristian: Thanks, updated that, it was pretty late. – Sam Dolan Feb 04 '11 at 18:39
  • Also note you can still use `print` instead of `sys.stdout.write` if you prefer: `print size_str + "\r",` works fine. The `,` at the end of the print statement suppresses the newline; `sys.stdout.flush()` is still needed – Jeff Nov 26 '16 at 00:38
18

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()
Andrea Spadaccini
  • 11,456
  • 3
  • 35
  • 52
  • 10
    unfortunately, curses is only available on Unix. the OP did not tell us which operating system his application is targeting... – Adrien Plisson Feb 04 '11 at 11:28
  • I'm so used to work under Linux that I didn't even notice the warning about the module being only for UNIX in the module docs.. Thanks for pointing that out, @Adrien. – Andrea Spadaccini Feb 04 '11 at 11:50
  • 3
    @AdrienPlisson, I know that this question was made years ago but, you can actually get Curses onto Windows: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses – Cold Diamondz Jun 18 '14 at 12:47
  • When I pasted this into my Python interpreter, my terminal became fubar. Exiting from the interpreter did not help. WTF?! – allyourcode Jan 14 '16 at 01:52
  • Use [`clrtoeol`](https://stackoverflow.com/a/20311594/2192488) for **when the second line is shorter than the first.** – Serge Stroobandt Jul 11 '17 at 09:05
  • If you're using Jupyter, this approach may mangle your kernel. – MattY Nov 04 '17 at 00:02
9

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("Foobar\nBazbar")
reprinter.reprint("Foo\nbar")
Bouke Versteegh
  • 2,797
  • 27
  • 27
  • 2
    This will not work in Windows up to Windows 10, because Windows 10 is the first windows to support those control characters https://en.wikipedia.org/wiki/ANSI_escape_code – user136036 Oct 03 '16 at 18:38
8

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.

Matt Ellen
  • 9,933
  • 4
  • 61
  • 80
  • 1
    Python 2.7.10, and it isn't printing anything. Maybe, you can compile it online and send us a link to see how it is working... ? – Display name Jan 17 '16 at 15:12
5

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'),
Moustafa Saleh
  • 178
  • 2
  • 6
4

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()
JoePythonKing
  • 479
  • 3
  • 13
4

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)
Amirouche Zeggagh
  • 2,532
  • 1
  • 17
  • 17