Using tqdm progress bar in a while loop

Question:

I am making a code that simulates a pawn going around a monopoly board a million times.
I would like to have a tqdm progress bar that is updated every time a turn around the board is achieved.

Below is my current code. I am using a while loop which stops when the number of turns around the board surpasses the desired number.

import os
from openpyxl import Workbook
from monopolyfct import *


def main(runs, fileOutput):

    ### EXCEL SETUP ###
    theWorkbook = Workbook()                              # Creates the workbook interface.
    defaultSheet = theWorkbook.active                     # Creates the used worksheet.
    currentData = ["Current Table Turn", "Current Tile"]  # Makes EXCEL column titles.
    defaultSheet.append(currentData)                      # Appends column titles.

    ### CONTENT SETUP ###
    currentData = [1, 0]             # Sets starting position.
    defaultSheet.append(currentData) # Appends starting position.

    while currentData[0] <= runs:

        ### ROLLING THE DICES PROCESS ###
        dices = twinDiceRoll()
        currentData[1] += dices[2]  # Updating the current tile

        ### SURPASSING THE NUMBER OF TILES ONBOARD ###
        if currentData[1] > 37:   # If more than a table turn is achieved,
            currentData[0] += 1   # One more turn is registered
            currentData[1] -= 38  # Update the tile to one coresponding to a board tile.
        else:
            pass

        ### APPENDING AQUIRED DATA ###
        defaultSheet.append(currentData)

        ### MANAGIING SPECIAL TILES ###
        if currentData[1] == 2 or 15 or 31:   # Community chess
            pass                              #TODO: Make a mechanic simulating the community chest card draw and it's related action.
        elif currentData[1] == 5 or 20 or 34: # Chance
            pass                              #TODO: Make a mechanic simulating the chance card draw and it's related action.
        elif currentData[1] == 28:            # Go to Jail
            pass                              #TODO: Make a mechanic simulating the entire jail process

        ### TWIN DICE ROLL EXCEPTION ###
        if dices[3] is True:  # If the dices roll a double,
            pass              #TODO: Make a mechanic considering that three doubles sends one to Jail.


    ### STORING THE ACCUMULATED DATA ###
    theWorkbook.save(fileOutput)  # Compiles the data in a .xlxs file.


if __name__ == "__main__":
    terminalWidth = os.get_terminal_size().columns                                               # Gets current terminal width.
    space(3)
    print("Python Monopoly Statistics Renderer".upper().center(terminalWidth))                   # Prints the title.
    print("(PMSR)".center(terminalWidth))                                                        # Prints the acronym.
    space(2)
    runs = int(request("For how many table turns do you want the simulation to run?"))           # Prompts for the desired run ammount
    #runs = 1000
    fileOutput = request("What should be the name of the file in which statistics are stored?")  # Prompts for the desired store filename
    #fileOutput = "test"
    fileOutput += ".xlsx"                                                                        # Adds file extension to filename
    main(runs, fileOutput)
Asked By: Benjamin Chausse

||

Answers:

You can use manual control in tqdm by specifying a total argument in the constructor. Verbatim from the manual:

with tqdm(total=100) as pbar:
    for i in range(10):
        sleep(0.1)
        pbar.update(10)

UPDATE

To manually control the tqdm without the context manager (aka with statement), you will need to close the progress bar after you are done using it. Here is another example from the manual:

pbar = tqdm(total=100)
for i in range(10):
    sleep(0.1)
    pbar.update(10)
pbar.close()

For this to work you need to know the total number of expected runs. In your code it could look something like

...
pbar = tqdm(total = runs+1)
while currentData[0] <= runs:

    ### ROLLING THE DICES PROCESS ###
    dices = twinDiceRoll()
    currentData[1] += dices[2]  # Updating the current tile

    ### SURPASSING THE NUMBER OF TILES ONBOARD ###
    if currentData[1] > 37:   # If more than a table turn is achieved,
        currentData[0] += 1   # One more turn is registered
        currentData[1] -= 38  # Update the tile to one coresponding to a board tile.
        pbar.update(1)
    else:
        pass
...
pbar.close()

However, this code isn’t perfect: consider if the currentData[1] is always less than 37 — the progress bar will just stop and not update. If you try to update it in the else:... part, you might violate the total upper bound. This is a start tho 🙂

Answered By: RafazZ

Because of the attention, this post is attracting I thought it would be good to point out how this can be achieved with an infinite while loop as well.

To use an infinite loop with tqdm you need to change your while loop into an infinite for loop by utilizing a generator.

Infinite loop (no progress bar)

while True:
  # Do stuff here

Infinite loop (with progress bar)

def generator():
  while True:
    yield

for _ in tqdm(generator()):
  # Do stuff here

The code above would create an indefinite progress bar that would look similar to this

16it [01:38,  6.18s/it]

Note that the generator could also be modified to work with a condition

def generator():
  while condition:
    yield
Answered By: user12128336

An additional version for user12128336’s answer.

You can do all iteration stuff inside a generator. Just add yield at the end of iteration.

from tqdm.auto import tqdm
from time import sleep


def generator():
    while True:
        sleep(0.3) # iteration stuff
        yield
        
for _ in tqdm(generator()): pass

# 77it [00:23,  3.25it/s]
Answered By: Ivan Reshetnikov

I know it’s been a while but this answer might help someone.
assume you want to read frames of video file using opencv, the usuall way is to create while loop as long as .IsOpened() returns True. something like :

def frame_extractor(video_file = video_file):
    cap = cv2.VideoCapture(video_file) 
    if not cap.isOpened(): 
        print("Error opening video stream or file")
    grabbed_frame = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if ret == True:
            grabbed_frame += 1
            cv2.imwrite(f'{grabbed_frame}.jpg',frame)
        else: 
          break
    cap.release()

if you know totall frames you can use tqdm as below :

all_frames = 1000
progress_bar = iter(tqdm(range(all_frames)))
def frame_extractor(video_file = video_file):
    cap = cv2.VideoCapture(video_file) 
    if not cap.isOpened(): 
        print("Error opening video stream or file")
    grabbed_frame = 0
    while cap.isOpened():
        next(progress_bar)
        ret, frame = cap.read()
        if ret == True:
            grabbed_frame += 1
            cv2.imwrite(f'{grabbed_frame}.jpg',frame)
        else: 
          break
    cap.release()

this only works if you know the length of your loop otherwise you should try one of above answers.

Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.