2

I need to make a progress bar in Python that prints out at x%.
So, for example, if the value is 62%, is there a way where it could print out something like this?

▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯ 62%

Or, for example, 23%

▮▮▮▮▮▮▮▮▮▮▮▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯ 23%
Alexei Dom
  • 137
  • 1
  • 1
  • 11

3 Answers3

4

A pure solution using the end parameter of print():

The print() statement allows you to specify what last chars are printed at the end of the call. By default, end is set to '\r\n' which is a carriage return and newline. This moves the 'cursor' to the start of the next line which is what you would want in most cases.

However for a progress bar, you want to be able to print back over the bar so you can change the current position and percentage without the end result looking bad over multiple lines.

To do this, we need to set the end parameter to just a carriage return: '\r'. This will bring our current position back to the start of the line so we can re-print over the progress bar.

As a demonstration of how this could work, I made this simple little code that will increase the progress bar by 1% every 0.5 seconds. The key part of the code is the print statement so this is the part that you will probably take away and put into your main script.

import time, math

bl = 50  #the length of the bar
for p in range(101):
   chars = math.ceil(p * (bl/100))
   print('▮' * chars + '▯' * (bl-chars), str(p) + '%', end='\r')
   time.sleep(0.5)

(n.b. the chars used here are copied from your question ('▮, ▯') and for me they did not print to the console in which case you will need to replace them with other ones for example: '█' and '.')

This code does what you want and steadily increases the percentage, here is an example of how it would look when p = 42:

▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▮▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯▯ 42%
Joe Iddon
  • 20,101
  • 7
  • 33
  • 54
3

Based on your question and Joe Iddon's answer I took it a step further based on my understanding from tinkering with tqdm. As noted by Irmen de Jong, if you don't mind using a library for this tqdm is cool.

A progressbar with gradient blocks + terminal rescaling awareness (python 3.3+)

This is an example solution if you want to build one from scratch (will work on Python 3.3+ for earlier versions tinkering is needed):

import os
import time

# This will only work for python 3.3+ due to use of
# os.get_terminal_size the print function etc.

FULL_BLOCK = '█'
# this is a gradient of incompleteness
INCOMPLETE_BLOCK_GRAD = ['░', '▒', '▓']

def progress_percentage(perc, width=None):
    assert(isinstance(perc, float))
    assert(0. <= perc <= 100.)
    # if width unset use full terminal
    if width is None:
        width = os.get_terminal_size().columns
    # progress bar is block_widget separator perc_widget : ####### 30%
    max_perc_widget = '[100.00%]' # 100% is max
    separator = ' '
    blocks_widget_width = width - len(separator) - len(max_perc_widget)
    assert(blocks_widget_width >= 10) # not very meaningful if not
    perc_per_block = 100.0/blocks_widget_width
    # epsilon is the sensitivity of rendering a gradient block
    epsilon = 1e-6
    # number of blocks that should be represented as complete
    full_blocks = int((perc + epsilon)/perc_per_block)
    # the rest are "incomplete"
    empty_blocks = blocks_widget_width - full_blocks

    # build blocks widget
    blocks_widget = ([FULL_BLOCK] * full_blocks)
    blocks_widget.extend([INCOMPLETE_BLOCK_GRAD[0]] * empty_blocks)
    # marginal case - remainder due to how granular our blocks are
    remainder = perc - full_blocks*perc_per_block
    # epsilon needed for rounding errors (check would be != 0.)
    # based on reminder modify first empty block shading
    # depending on remainder
    if remainder > epsilon:
        grad_index = int((len(INCOMPLETE_BLOCK_GRAD) * remainder)/perc_per_block)
        blocks_widget[full_blocks] = INCOMPLETE_BLOCK_GRAD[grad_index]

    # build perc widget
    str_perc = '%.2f' % perc
    # -1 because the percentage sign is not included
    perc_widget = '[%s%%]' % str_perc.ljust(len(max_perc_widget) - 3)

    # form progressbar
    progress_bar = '%s%s%s' % (''.join(blocks_widget), separator, perc_widget)
    # return progressbar as string
    return ''.join(progress_bar)

def test_pbar(width=None):
    import random
    random.seed(42)
    i = 0
    while(i < 10000):
        print(progress_percentage(i/100, width=width), end='\r')
        time.sleep(0.25)
        i += random.randrange(3000)
    print(progress_percentage(100.00, width=width), end='\r')
    print('\nDone!')

if __name__ == "__main__":
    print('=== First test with width = 20 ===')
    test_pbar(width=20)
    print('=== Now with terminal width ===')
    test_pbar(width=None)

Here is what the output should look like when run with different terminal sizes (tmux panes): Code run in different terminal sizes (tmux panes)

Andreas Grivas
  • 337
  • 4
  • 6
2

I've always found the tqdm library very handy for this. It's small, has low overhead and is very simple to use: simply wrapping an iterable often does the job already.

Example

from tqdm import tqdm
for i in tqdm(range(10000)):
    ...
Irmen de Jong
  • 2,739
  • 1
  • 14
  • 26