在while循环中使用tqdm进度条

132
我正在制作一个代码,模拟了一百万次兵卒绕着monopoly棋盘转的情况。 我想要一个tqdm进度条,每当完成一圈后更新一次。

以下是我的目前代码。 我使用while循环,当圆周数量超过所期望的数量时停止。

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)

如果您事先知道“runs”的值,为什么不直接使用for循环呢? - Addison Klinke
4个回答

223

你可以在 tqdm 构造函数中指定一个 total 参数来使用手动控制。摘自手册

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

更新

如果您想手动控制 tqdm 进度条而不使用上下文管理器(也称 with 语句),您需要在完成后关闭进度条。这是手册中的另一个示例:

pbar = tqdm(total=100)
for i in range(10):
    sleep(0.1)
    pbar.update(10)
pbar.close()
为了使这个工作正常,您需要知道预期运行的总次数。在您的代码中,它可能看起来像这样:
...
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()

然而,这段代码并不完美:考虑如果currentData[1]始终小于37——进度条将停止更新。如果你尝试在else:...部分更新它,可能会违反total的上限。不过这只是一个开始 :)


14
为了使上述代码正常工作,您需要使用 from tqdm import tqdm 或者 pbar = tqdm.tqdm(total=100) - Jello
6
更好的做法是,使用with tqdm(total=100) as pbar: do... ; pbar.update(10);其中 with 语句会自动管理进度条的创建和清理,而 pbar.update(10) 则表示完成了进度条的 10%。 - Sylvain
非常有用,谢谢! - Lorenzo Bassetti
为什么需要睡眠? - mrgloom
@mrgloom 这不是错误--通常我们会在程序运行过快时加入 sleep,以展示它在真实场景中的表现,比如循环内的某些操作需要很长时间。 - RafazZ
4
请注意,pbar.update(...)的参数是增加进度条的量,而不是将其设置为绝对值。 - Peter

35
由于这篇文章引起了人们的关注,我认为指出如何使用无限while循环也是有益的。
要使用tqdm的无限循环,您需要通过利用生成器将while循环更改为无限for循环。
无限循环(没有进度条)
while True:
  # Do stuff here

无限循环(带进度条)

def generator():
  while True:
    yield

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

上面的代码将创建一个无限进度条,看起来类似于这个样子

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

请注意,生成器也可以被修改以适应条件。
def generator():
  while condition:
    yield

1
确实是一个简洁的解决方案。 - SanMelkote
这很好,但是最后出现了“RuntimeError:无法加入当前线程”的错误。 有人知道如何处理吗? - Eypros
3
这也可以不使用生成器:progress = tqdm(),然后在循环内部调用progress.update()而不带参数。 - luator

1

用户12128336的答案的另一个版本。

您可以在生成器内部执行所有迭代操作。只需在迭代结束时添加yield

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]

1

我知道已经有一段时间了,但这个答案可能会帮助到某些人。 假设您想使用OpenCV读取视频文件的帧,通常的方法是创建一个while循环,只要.IsOpened()返回True。类似于:

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

如果您知道总帧数,可以按以下方式使用tqdm:
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()

只有在您知道循环长度的情况下,此方法才有效,否则您应该尝试上面的答案之一。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接