Alex
Alex

Reputation: 21

Getting user input within tqdm loops

I'm writing a script where a user has to provide input for each element of a large list. I'm trying to use tqdm to provide a progress bar for the user, but I can't find a good way to get input within the tqdm loop without breaking the output.

I'm aware of tqdm.write() for writing to the terminal during a tqdm loop, but is there a way of getting input?

For an example of what I'm trying to do, consider the code below:

from tqdm import tqdm
import sys
from time import sleep

def do_stuff(x): sleep(0.5)

stuff_list = ['Alpha', 'Beta', 'Gamma', 'Omega']

for thing in tqdm(stuff_list):
    input_string = input(thing + ": ")
    do_stuff(input_string)

If I run this code, I get the following output:

0%|                                                                                            | 0/4 [00:00<?, ?it/s]Alpha: A
 25%|█████████████████████                                                               | 1/4 [00:02<00:07,  2.54s/it]Beta: B
 50%|██████████████████████████████████████████                                          | 2/4 [00:03<00:04,  2.09s/it]Gamma: C
 75%|███████████████████████████████████████████████████████████████                     | 3/4 [00:04<00:01,  1.72s/it]Omega: D
100%|████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:05<00:00,  1.56s/it]

I've tried using tqdm.external_write_mode, but this simply didn't display the progress bar whenever an input was waiting, which is not the behaviour I'm looking for.

Is there an easy way of doing this, or am I going to have to swap libraries?

Upvotes: 2

Views: 1482

Answers (1)

Finomnis
Finomnis

Reputation: 22773

It isn't possible to display the progress bar while inside the input() function, because once a line is finished, it cannot be removed any more. It's a technical limitation of how command lines work. You can only remove the current line until you wrote a newline.

Therefore, I think the only solution is to remove the status bar, let the user input happen and then display it again.

from tqdm import tqdm
import sys
from time import sleep

def do_stuff(x): sleep(0.5)

stuff_list = ['Alpha', 'Beta', 'Gamma', 'Omega']

# To have more fine-control, you need to create a tqdm object
progress_iterator = tqdm(stuff_list)

for thing in progress_iterator:
    # Remove progress bar
    progress_iterator.clear()

    # User input
    input_string = input(thing + ": ")

    # Write the progress bar again
    progress_iterator.refresh()

    # Do stuff
    do_stuff(input_string)

If you don't like the fact that the progress_iterator object exists after the loop, use the with syntax:

with tqdm(stuff_list) as progress_iterator:
    for thing in progress_iterator:
        ...

EDIT: If you are willed to sacrifice platform independence, you can freely move the cursor and delete lines with this:

from tqdm import tqdm
import sys
from time import sleep

def do_stuff(x): sleep(0.5)

stuff_list = ['Alpha', 'Beta', 'Gamma', 'Omega']

# Special console commands
CURSOR_UP_ONE = '\x1b[1A'

# To have more fine-control, you need to create a tqdm object
progress_iterator = tqdm(stuff_list)

for thing in progress_iterator:

    # Move the status bar one down
    progress_iterator.clear()
    print(file=sys.stderr)
    progress_iterator.refresh()

    # Move the cursor back up
    sys.stderr.write('\r')
    sys.stderr.write(CURSOR_UP_ONE)

    # User input
    input_string = input(thing + ": ")

    # Refresh the progress bar, to move the cursor back to where it should be.
    # This step can be omitted.
    progress_iterator.refresh()

    # Do stuff
    do_stuff(input_string)

I think this is the closest you will get to tqdm.write(). Note that the behaviour of input() can never be identical to tqdm.write(), because tqdm.write() first deletes the bar, then writes the message, and then writes the bar again. If you want to display the bar while being in input(), you have to do some platform-dependent stuff like this.

Upvotes: 3

Related Questions