160

I'm wondering how I could create one of those nifty console counters in Python as in certain C/C++-programs.

I've got a loop doing things and the current output is along the lines of:

Doing thing 0
Doing thing 1
Doing thing 2
...

what would be neater would be to just have the last line update;

X things done.

I've seen this in a number of console programs and am wondering if/how I'd do this in Python.

sorin
  • 161,544
  • 178
  • 535
  • 806
dutt
  • 7,909
  • 11
  • 52
  • 85

12 Answers12

202

An easy solution is just writing "\r" before the string and not adding a newline; if the string never gets shorter this is sufficient...

sys.stdout.write("\rDoing thing %i" % i)
sys.stdout.flush()

Slightly more sophisticated is a progress bar... this is something I am using:

def start_progress(title):
    global progress_x
    sys.stdout.write(title + ": [" + "-"*40 + "]" + chr(8)*41)
    sys.stdout.flush()
    progress_x = 0

def progress(x):
    global progress_x
    x = int(x * 40 // 100)
    sys.stdout.write("#" * (x - progress_x))
    sys.stdout.flush()
    progress_x = x

def end_progress():
    sys.stdout.write("#" * (40 - progress_x) + "]\n")
    sys.stdout.flush()

You call start_progress passing the description of the operation, then progress(x) where x is the percentage and finally end_progress()

Neuron
  • 5,141
  • 5
  • 38
  • 59
6502
  • 112,025
  • 15
  • 165
  • 265
  • 2
    What if the string is shorter than the previous one? – math2001 Oct 14 '16 at 20:22
  • 7
    @math2001 padding with whitespace. – felipsmartins Feb 06 '17 at 18:32
  • 1
    Voted for only first 2 lines of code. The progress bar part is becoming slow in a few cases. Anyway Thanks @6502 – WaterRocket8236 Nov 22 '17 at 07:00
  • Some programs (`restic`, `flatpak`) can update several lines of console output. Do you know by any chance how this can be achieved? – Alexey Mar 12 '19 at 21:04
  • 1
    @Alexey: you can use ANSI escape codes to move cursor around, clear portions of screen and change colors... see https://en.wikipedia.org/wiki/ANSI_escape_code – 6502 Mar 12 '19 at 21:51
  • In the definition of ```startProgress``` line 2 should be ```sys.stdout.write(title + ": [")``` – Ahsan Ahmed Aug 24 '19 at 15:09
  • No, it shouldn't @AhsanAhmed – ch4rl1e97 Dec 10 '19 at 19:08
  • can you explain `chr(8)*41`? how does it move the cursor back by 41 characters? – Anthony Apr 25 '21 at 17:48
  • @Anthony: `chr(8)` is ASCII `backspace` character (also often displayed as `ctrl-H` or `^H`) and the effect is of "going back" one character. In other words if your display the string `"ABC"+chr(8)+"DEF"` the final visible result in most terminals will be `ABDEF` because after writing `C` the `backspace` will back up one position and the `D` will end up overwriting the `C`. – 6502 Apr 26 '21 at 06:19
71

A more elegant solution could be:

def progress_bar(current, total, bar_length=20):
    fraction = current / total

    arrow = int(fraction * bar_length - 1) * '-' + '>'
    padding = int(bar_length - len(arrow)) * ' '

    ending = '\n' if current == total else '\r'

    print(f'Progress: [{arrow}{padding}] {int(fraction*100)}%', end=ending)

Call this function with current and total:

progress_bar(69, 100)

The result should be

Progress: [------------->      ] 69%

Note:

Aravind Voggu
  • 1,491
  • 12
  • 17
44

In python 3 you can do this to print on the same line:

print('', end='\r')

Especially useful to keep track of the latest update and progress.

I would also recommend tqdm from here if one wants to see the progress of a loop. It prints the current iteration and total iterations as a progression bar with an expected time of finishing. Super useful and quick. Works for python2 and python3.

Joop
  • 3,706
  • 34
  • 55
24

I wrote this a while ago and really happy with it. Feel free to use it.

It takes an index and total and optionally title or bar_length. Once done, replaces the hour glass with a check-mark.

⏳ Calculating: [████░░░░░░░░░░░░░░░░░░░░░] 18.0% done

✅ Calculating: [█████████████████████████] 100.0% done

I included an example that can be run to test it.

import sys
import time

def print_percent_done(index, total, bar_len=50, title='Please wait'):
    '''
    index is expected to be 0 based index. 
    0 <= index < total
    '''
    percent_done = (index+1)/total*100
    percent_done = round(percent_done, 1)

    done = round(percent_done/(100/bar_len))
    togo = bar_len-done

    done_str = '█'*int(done)
    togo_str = '░'*int(togo)

    print(f'\t⏳{title}: [{done_str}{togo_str}] {percent_done}% done', end='\r')

    if round(percent_done) == 100:
        print('\t✅')


r = 50
for i in range(r):
    print_percent_done(i,r)
    time.sleep(.02)

I also have a version with responsive progress bar depending on the terminal width using shutil.get_terminal_size() if that is of interest.

12

It can be done without using the sys library if we look at the print() function

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

Here is my code:

def update(n):
    for i in range(n):
        print("i:",i,sep='',end="\r",flush=True)
        time.sleep(1)
Suman Saurabh
  • 401
  • 5
  • 9
  • the only thing I'd add is: columns, lines = os.get_terminal_size() message = f'{i} hello!' print(f'{message:<{columns}}', sep='', end='\r', flush=True) – Ryan Loggerythm Oct 12 '21 at 17:19
8

For anyone who stumbles upon this years later (like I did), I tweaked 6502's methods a little bit to allow the progress bar to decrease as well as increase. Useful in slightly more cases. Thanks 6502 for a great tool!

Basically, the only difference is that the whole line of #s and -s is written each time progress(x) is called, and the cursor is always returned to the start of the bar.

def startprogress(title):
    """Creates a progress bar 40 chars long on the console
    and moves cursor back to beginning with BS character"""
    global progress_x
    sys.stdout.write(title + ": [" + "-" * 40 + "]" + chr(8) * 41)
    sys.stdout.flush()
    progress_x = 0


def progress(x):
    """Sets progress bar to a certain percentage x.
    Progress is given as whole percentage, i.e. 50% done
    is given by x = 50"""
    global progress_x
    x = int(x * 40 // 100)                      
    sys.stdout.write("#" * x + "-" * (40 - x) + "]" + chr(8) * 41)
    sys.stdout.flush()
    progress_x = x


def endprogress():
    """End of progress bar;
    Write full bar, then move to next line"""
    sys.stdout.write("#" * 40 + "]\n")
    sys.stdout.flush()
jat255
  • 667
  • 7
  • 18
  • 1
    I've found though, that this can cause some slowdowns if it is called too frequently by the code, so I guess YMMV – jat255 Jan 08 '14 at 23:22
7

The other answer may be better, but here's what I was doing. First, I made a function called progress which prints off the backspace character:

def progress(x):
    out = '%s things done' % x  # The output
    bs = '\b' * 1000            # The backspace
    print bs,
    print out,

Then I called it in a loop in my main function like so:

def main():
    for x in range(20):
        progress(x)
    return

This will of course erase the entire line, but you can mess with it to do exactly what you want. I ended up make a progress bar using this method.

Bryce Siedschlaw
  • 4,136
  • 1
  • 24
  • 36
  • 4
    Works, but if the previous line had more characters than the next, the characters after the end of the new line remain from the previous line: "Spell checking record 417/701 [serfice changed to surface]when] uminescence] cence] shmentarianism]" – Lil' Bits Jan 26 '16 at 03:44
6

If I understood well (not sure) you want to print using <CR> and not <LR>?

If so this is possible, as long the console terminal allows this (it will break when output si redirected to a file).

from __future__ import print_function
print("count x\r", file=sys.stdout, end=" ")
sorin
  • 161,544
  • 178
  • 535
  • 806
5

Added a little bit more functionality to the example of Aravind Voggu:

def progressBar(name, value, endvalue, bar_length = 50, width = 20):
        percent = float(value) / endvalue
        arrow = '-' * int(round(percent*bar_length) - 1) + '>'
        spaces = ' ' * (bar_length - len(arrow))
        sys.stdout.write("\r{0: <{1}} : [{2}]{3}%".format(\
                         name, width, arrow + spaces, int(round(percent*100))))
        sys.stdout.flush()
        if value == endvalue:     
             sys.stdout.write('\n\n')

Now you are able to generate multiple progressbars without replacing the previous one.

I've also added name as a value with a fixed width.

For two loops and two times the use of progressBar() the result will look like:

progress bar animation

Neuron
  • 5,141
  • 5
  • 38
  • 59
Nils
  • 409
  • 6
  • 16
3
from time import sleep

max_val = 40

for done in range(max_val):
    sleep(0.05)

    undone = max_val - 1 - done
    proc = (100 * done) // (max_val - 1)
    print(f"\rProgress: [{('#' * done) + ('_' * undone)}] ({proc}%)", end='\r')

print("\nDone!")
Progress: [###################_____________________] (47%)
Progress: [########################################] (100%)
Done!
V1A0
  • 41
  • 2
-1

Below code will count Message from 0 to 137 each 0.3 second replacing previous number.

Number of symbol to backstage = number of digits.

stream = sys.stdout
for i in range(137):
    stream.write('\b' * (len(str(i)) + 10))
    stream.write("Message : " + str(i))
    stream.flush()
    time.sleep(0.3)
laggerok19
  • 426
  • 8
  • 16
-1

Had the same problem and tried many solutions.

import sys 
sys.stdout.write('\r Blablabla')

worked like a charm!