Search code examples
pythonprintingparallel-processingpercentagelines

How can I print multiple lines, having subsequent lines replacing each of them independently (Python)


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.


Solution

  • 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)