0

I am running a program that works in parallel, utilizing the Pool object from the multiprocessing module.

What I am trying to do is run a function n number of times in parallel, each having a separate loading %, and I would like it to be updating the percentage for each function without replacing other percentages... example:

f(x):
    while x < 0:
        print 'For x = {0}, {1}% completed...\r'.format(x, percentage),

And I would run the function multiple times in parallel.

The effect I am trying to achieve is the following, for f(10000000), f(15000000), f(7000000):

For x = 10000000, 43% completed
For x = 15000000, 31% completed
For x = 7000000, 77% completed

And the percentages will be updating in their individual lines without replacing for other values of x, in this function f which will be running three times at the same time.

I tried using the carriage return '\r' but that replaces every line and just creates a mess.

Thanks for taking the time to read this post! I hope you can tell me.

I am using Python 2.7 but if it can only be achieved with Python 3 I am open to suggestions.

B. Morfe
  • 61
  • 4
  • have you checked out curses library? – joel goldstick Aug 03 '16 at 18:11
  • I'm not sure if it will support multiple concurrent status bars, but you might look at the library referenced here: http://stackoverflow.com/questions/3002085/python-to-print-out-status-bar-and-percentage – Keozon Aug 03 '16 at 18:14
  • store the output in array, flush output completed, print array again. I'm not sure, just suggestion. I guess, that is what you are searching for! – gwthm.in Aug 03 '16 at 18:29

1 Answers1

2

Curses

As @Keozon mentioned in the comments, one way to achieve this would be to use the curses library.

There is a good guide for curses on the python website.

ANSI Escape Codes

Alternatively, you might try using ANSI escape codes to move the cursor around.

This is in Python3, but it'll work just fine in any version, you'll just need to change the print statements around (or from __future__ import print_function).

print('Hello')                                     
print('World')                                     
                                                   
print('\033[F\033[F\033[K', end='') # Up, Up, Clear line
                                                   
# Cursor is at the 'H' of 'Hello'                  
                                                   
print('Hi') # Overwriting 'Hello'                  
                                                   
# Cursor is at the 'W' of 'World'                  
                                                   
print('\033[E', end='') # Down                     
                                                   
# Cursor is on the blank line after 'World'        
                                                   
print('Back to the end')   

                    
              

Output:

Hi         
World          
Back to the end

Edit:

I've done way too much work for you here, but hey, here's basically a full solution using the ANSI method I mentioned above:

import time
import random


class ProgressBar:
    def __init__(self, name):
        self._name = name
        self._progress = 0

    @property
    def name(self):
        return self._name

    def get_progress(self):
        """
        Randomly increment the progress bar and ensure it doesn't go
        over 100
        """
        self._progress += int(random.random()*5)
        if self._progress > 100:
            self._progress = 100

        return self._progress


class MultipleProgressBars:
    def __init__(self, progress_bars):
        self._progress_bars = progress_bars
        self._first_update = True
        self._all_finished = False

    @property
    def all_finished(self):
        """
        A boolean indicating if all progress bars are at 100
        """
        return self._all_finished

    def update(self):
        """
        Update each progress bar
        """
        # We don't want to move up and clear a line on the first run
        # so we have a flag to make sure this only happens on later
        # calls
        if not self._first_update:
            # Move up and clear the line the correct number of times
            print('\033[F\033[K'*len(self._progress_bars),end='', sep='')

        num_complete = 0  # Number of progress bars complete
        for progress_bar in self._progress_bars:
            name = progress_bar.name
            progress = progress_bar.get_progress()

            if progress == 100:
                num_complete += 1

            # Print out a progress bar (scaled to 40 chars wide)
            print(
                name.ljust(10),
                '[' + ('='*int(progress*0.4)).ljust(40) + ']',
                str(progress)+'%')

        if num_complete == len(self._progress_bars):
            self._all_finished = True

        self._first_update = False  # Mark the first update done


# Create a list of ProgressBars and give them relevant names
progress_bars = [
    ProgressBar('James'),
    ProgressBar('Bert'),
    ProgressBar('Alfred'),
    ProgressBar('Frank')
]

# Create a new instance of our MultipleProgressBars class
mpb = MultipleProgressBars(progress_bars)

# Keep updating them while at least one of them is still active
while not mpb.all_finished:
    mpb.update()
    time.sleep(0.2)
Community
  • 1
  • 1
OdinX
  • 4,135
  • 1
  • 24
  • 33
  • Thanks a lot! I will go try that later and come back with the results! – B. Morfe Aug 03 '16 at 19:18
  • Note that this merely writes on top, so writing `hello` on top of `pancake` would result in `helloke`. I believe it is `\033[K` that clears everything from the cursor to the end of the line. – zondo Aug 03 '16 at 19:35
  • @zondo Yea, you're right. I've updated my answer to rectify this. – OdinX Aug 03 '16 at 20:06
  • Thanks again for your answer. I really appreciate it. The issue I have is that since the function is running in parallel, there is no way to there where each % for each different x will be, so I cannot know which line I want to replace in each print. For example: The function is printing `some % loaded for x = some number`, and is randomly printing since it's all running at the same time. The only thing I could come up with is selecting a printing line for `x = some number` but that would be if I knew each value of `x`, making it inefficient. – B. Morfe Aug 04 '16 at 02:13
  • You'll probably have to change the way it's working to get it to work as you want it to unfortunately. Just in case you're not aware, python has a [GIL](https://wiki.python.org/moin/GlobalInterpreterLock) this means that depending on what you're using threading for, you mightn't actually get a performance benefit. However, "potentially blocking or long-running operations, such as I/O, image processing, and NumPy number crunching, happen outside the GIL", so it depends on what you're doing in your threads. – OdinX Aug 04 '16 at 11:59