0

I am using this answer to print a progress bar but want it to print what exactly it is doing while it is progressing. I added a parameter called "current_task" to print_progress() and now would like it to perform as follows. How do I do this?

FYI: I'm on on a Unix system: macOS Sierra

print_progress(7,10,...remaining params..., "downloading contacts")

should print this

Currently downloading contacts
Progress |████████████████████████████████---------------------| 70% Complete

the subsequent call of

print_progress(8,10,...remaining params..., "downloading companies")

should cause the progress bar to change in place to now look like this

Currently downloading companies
Progress |████████████████████████████████████-------------| 80% Complete

herteladrian
  • 381
  • 1
  • 6
  • 18
  • Hi @Greenstick,It would be great to get your input on this as you were the one that wrote the print_progress code I'm using. Much appreciated! – herteladrian Oct 15 '17 at 03:38
  • Greenstick won't get a notification for your comment because he hasn't posted on this page. I guess you could put a comment on the answer he wrote, politely directing him here, but please bear in mind that he's under no obligation to help you with this. – PM 2Ring Oct 15 '17 at 04:09
  • @PM2Ring Running this on a Unix system! (macOS Sierra to be specific). I just updated the question to reflect this. and thanks for letting me know about the way notifications work. And yes I understand he's under no obligation. Didn't think it hurt to ask though. – herteladrian Oct 15 '17 at 04:41
  • @PM2Ring I think I had the positional arg part down but couldn't get it to print properly. I appreciate your help! Thanks! – herteladrian Oct 15 '17 at 04:46

1 Answers1

2

Here's a modified version of Greenstick's code that supports a header line. It uses an ANSI control sequence '\x1b[3A' to move the terminal cursor up 3 lines after it's printed the header & progress bar.

This updated version works correctly on Python 2 (tested on 2.6.6) & Python 3 (tested on 3.6.0). It also erases the previous contents of the header line so you don't get stray characters if the current header is shorter than the previous one.

from __future__ import print_function
from time import sleep

# Print iterations progress
#Originally written by Greensticks, modified by PM 2Ring
def printProgressBar (iteration, total, prefix='', suffix='', decimals=1, 
    length=100, fill=u'\u2588', header=''):
    """
    Call in a loop to create terminal progress bar
    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)
        decimals    - Optional  : positive number of decimals in percent complete (Int)
        length      - Optional  : character length of bar (Int)
        fill        - Optional  : bar fill character (Str)
        header      - Optional  : header string (Str)
    """
    # Clear the current line and print the header
    print('\x1b[2K', header, '\n')
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filledLength = int(length * iteration // total)
    # Generate and print the bar
    bar = fill * filledLength + u'-' * (length - filledLength)
    print('%s |%s| %s%% %s\x1b[3A' % (prefix, bar, percent, suffix))
    # Print New Lines on Complete
    if iteration == total: 
        print('\n' * 2)

# Test

maxi = 10
delay = 0.5

# Initial call to print 0% progress
header = 'Currently downloading contacts now'
printProgressBar(0, maxi, prefix='Progress:', suffix='Complete', length=50, header=header)
for i in range(1, 8):
    # Do stuff...
    sleep(delay)
    # Update Progress Bar
    printProgressBar(i, maxi, prefix='Progress:', suffix='Complete', length=50, header=header)

header = 'Currently downloading companies'
for i in range(8, maxi + 1):
    # Do stuff...
    sleep(delay)
    # Update Progress Bar
    printProgressBar(i, maxi, prefix='Progress:', suffix='Complete', length=50, header=header)

print('Finished')

Note that if you don't supply a header line you'll get a blank header line. Please make sure that the header line will actually fit on one line of your terminal, and definitely don't put any '\n' chars in it!


You could make this progress bar more versatile by using threading, as illustrated in this Scrolling Timer I wrote a few months ago.


Here's a version of printProgressBar that disables the cursor so we don't need that extra pace at the start of the cursor.

def printProgressBar (iteration, total, prefix='', suffix='', decimals=1, 
    length=100, fill=u'\u2588', header=''):
    """
    Call in a loop to create terminal progress bar
    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)
        decimals    - Optional  : positive number of decimals in percent complete (Int)
        length      - Optional  : character length of bar (Int)
        fill        - Optional  : bar fill character (Str)
        header      - Optional  : header string (Str)
    """
    if iteration == 0:
        # Turn off the cursor
        print("\x1b[?25l", end='')
    # Clear the current line & print the header
    print('\x1b[2K', header, sep= '', end='\n\n')
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filledLength = int(length * iteration // total)
    # Generate and print the bar
    bar = fill * filledLength + u'-' * (length - filledLength)
    print('%s |%s| %s%% %s\x1b[3A' % (prefix, bar, percent, suffix))
    # Print New Lines on Complete
    if iteration == total: 
        # Turn on the cursor, and skip a few lines
        print("\x1b[?25h", end='\n\n')

One problem with doing this is that if we terminate the program early (eg by hitting CtrlC) while the cursor is disabled, it will still be disabled after the program edits. On Linux, you can just send the ANSI sequence to turn the cursor back on with a simple Bash command:

echo -e "\e[?25h"

although it's easier to reset the terminal:

echo -e "\ec"

Of course, we could also trap signal.SIGINT and add a handler function to turn the cursor on before the program exits, but that adds extra complexity to the code.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • 1
    I had to change the header print line to print(header) and use `'\x1b[2A'` (changed 3 to 2). Works for me now! – herteladrian Oct 15 '17 at 05:12
  • Oooh. Interesting Scrolling Timer! Will look into that though is definitely advanced for me. – herteladrian Oct 15 '17 at 05:13
  • Sorry! that was confusing. I was having trouble formatting it correctly but figured out how and update the question. I am still having issues with your solution unfortunately. It displays a grey cursor and leaves chars in the terminal window if the previous header was longer than the current one. See [here](https://imgur.com/a/TNQWo) for picture – herteladrian Oct 15 '17 at 07:58
  • 1
    @herteladrian Ok. Sorry about the stray chars in the header line if the previous header was longer than the current one. I'll see what I can do about that shortly. But you shouldn't get a grey cursor over the top of the first char in the header: I intentionally left a space before the header text to make room for the cursor: `print('', header, '\n')` – PM 2Ring Oct 15 '17 at 08:03
  • No need to be sorry! I greatly appreciate the help :) I am using python 2.7 so when I used that exact code it would literally print _('', header, '\n')_. Did you mean to but a space between those single quotes. When I copy and paste that print statement there is nothing between the single quotes. If I add a space there, a grey cursor hovers above an empty space before the header. Is that how you meant for it to work? – herteladrian Oct 15 '17 at 08:15
  • @herteladrian Sorry for the delay. I had a visitor. – PM 2Ring Oct 15 '17 at 09:00
  • @herteladrian I assumed you're using Python 3 because you didn't mention which version you're using. You can use the Python 3 `print` function in Python 2 by putting this import statement at the top of your script, before any other imports: `from __future__ import print_function`. It's more powerful than the old `print` statement. – PM 2Ring Oct 15 '17 at 09:04
  • @herteladrian In `print('', header, '\n')` the 1st arg is an empty string, because `print` will insert a space between each arg. So yes, there's supposed to be a single space before the header line to hold the cursor. An alternative would be to make the cursor invisible, but then we have to turn the cursor back on before the program exits. – PM 2Ring Oct 15 '17 at 09:04
  • @herteladrian I've fixed my code, and I also added a version that disables the cursor, although I don't recommend it. – PM 2Ring Oct 15 '17 at 09:32
  • Interesting adding in the header – it's not something I had considered before. If I may ask, why not just print the header before calling the progress bar? – Greenstick Oct 16 '17 at 00:38
  • @Greenstick Maybe I'm misunderstanding your question, but I did it like that because herteladrian wants to pass the header as an arg to the printProgressBar function, and that header may change over subsequent calls to the same progress bar. – PM 2Ring Oct 16 '17 at 01:01
  • @Greenstick The goal was to have the progress bar stay in place and at each incremental `print_progress(...)` call, the header above it changes too. Is this easily achieved with just a `print (header)` statement somewhere? – herteladrian Oct 16 '17 at 07:38
  • 1
    Ah I see, that makes sense. I was asking because it seemed outside the scope of a progress bar function. I originally included the suffix / prefix parameters to account for that functionality in most cases, but it seems this was an edge case. Good to see a nice solution was arrived at : ) – Greenstick Oct 16 '17 at 22:14