在函数内部使用tqdm监测for循环的进度

10

我正在使用for循环遍历目录树中的大量文件。

在这样做的同时,我希望通过控制台上的进度条来监视进度。因此,我决定使用tqdm来实现这个目的。

目前,我的代码如下:

for dirPath, subdirList, fileList in tqdm(os.walk(target_dir)):
        sleep(0.01)
        dirName = dirPath.split(os.path.sep)[-1]
        for fname in fileList:
        *****

输出:

Scanning Directory....
43it [00:23, 11.24 it/s]

我的问题是它没有显示进度条。我想知道如何正确使用它并更好地理解它的工作原理。此外,如果有其他可以在此处使用的tqdm替代方案,请告诉我。

6个回答

9

如果你不知道“完成”是什么意思,就不能展示“百分比完成”。

os.walk 运行时,它不知道要迭代多少个文件和文件夹: os.walk 的返回类型没有 __len__。为了计算它们的数量,它必须一直查看整个目录树,枚举所有文件和文件夹。换句话说,在告诉你它将生成多少项之前,os.walk 必须做两次所有的工作,这是低效的。

如果你非常坚定地想要显示进度条,你可以将数据缓存到内存列表中:list(os.walk(target_dir))。但我不推荐这样做。如果你遍历一个大的目录树,这会消耗大量的内存。更糟糕的是,如果 followlinksTrue,并且你有一个循环的目录结构(子目录链接到他们的父目录),那么它可能会一直循环直到内存用完。


9
这里有一个更加简洁的预计算文件数量并在文件上提供状态栏的方法:
file_count = sum(len(files) for _, _, files in os.walk(folder))  # Get the number of files
with tqdm(total=file_count) as pbar:  # Do tqdm this way
    for root, dirs, files in os.walk(folder):  # Walk the directory
        for name in files:
            pbar.update(1)  # Increment the progress bar
            # Process the file in the walk

2
使用生成器来计算文件数量非常棒!此外,这必须是首选答案! - nikhilweee

3
文档中所述,这是因为您需要提供进度指示器。根据您对文件的操作,您可以使用文件计数或文件大小。
其他答案建议将os.walk()生成器转换为列表,以便获得__len__属性。然而,这将根据您拥有的文件总数消耗大量内存。
另一种可能性是进行预计算:首先遍历整个文件树并计算文件总数(但不保留文件列表,只保留计数!),然后您可以再次遍历并提供预先计算的文件计数给tqdm
def walkdir(folder):
    """Walk through every files in a directory"""
    for dirpath, dirs, files in os.walk(folder):
        for filename in files:
            yield os.path.abspath(os.path.join(dirpath, filename))

# Precomputing files count
filescount = 0
for _ in tqdm(walkdir(target_dir)):
    filescount += 1

# Computing for real
for filepath in tqdm(walkdir(target_dir), total=filescount):
        sleep(0.01)
        # etc...

请注意,我在os.walkdir上定义了一个包装函数:由于您正在处理的是文件而不是目录,因此最好定义一个函数,该函数将进展到文件而不是目录。
但是,如果不使用walkdir包装器,您也可以获得相同的结果,但是需要在遍历每个子文件夹后恢复上一个进度条状态,这会更加复杂。
# Precomputing
filescount = 0
for dirPath, subdirList, fileList in tqdm(os.walk(target_dir)):
    filescount += len(filesList)

# Computing for real
last_state = 0
for dirPath, subdirList, fileList in os.walk(target_dir):
    sleep(0.01)
    dirName = dirPath.split(os.path.sep)[-1]
    for fname in tqdm(fileList, total=filescount, initial=last_state):
        # do whatever you want here...
    # Update last state to resume the progress bar
    last_state += len(fileList)

2

这是因为tqdm不知道os.walk的结果会有多长,因为它是一个生成器,所以不能对其调用len。您可以通过先将os.walk(target_dir)转换为列表来解决此问题:

for dirPath, subdirList, fileList in tqdm(list(os.walk(target_dir))):

根据 tdqm 模块的文档:

如果可能,会使用 len(iterable)。作为最后的手段,只显示基本进度统计(没有 ETA,没有进度条)。

但是,len(os.walk(target_dir)) 不可行,因此没有 ETA 或进度条。

正如 Benjamin 指出的那样,使用 list 会占用一些内存,但不会太多。在我的 Windows 10 机器上,使用此代码处理大约 190,000 个文件的文件夹时,Python 大约使用了 65MB 的内存。


1
65MB的内存已经相当大了!如果您的目录结构是循环的,那么它将使用无限的内存,这甚至比65MB还要多。 - Benjamin Hodgson

1

你可以使用 tqdm 实现对某个目录下所有文件进度的追踪。

from tqdm import tqdm
target_dir = os.path.join(os.getcwd(), "..Your path name")#it has 212 files
for r, d, f in os.walk(target_dir):
    for file in tqdm(f, total=len(f)):
        filepath = os.path.join(r, file)
        #f'Your operation on file..{filepath}'

20%|████████████████████ | 42/212 [05:07<17:58, 6.35s/it]

就像这样,您将获得进度...


0

这是我对类似问题的解决方案:

    for root, dirs, files in os.walk(local_path):
        path, dirs, files = os.walk(local_path).next()
        count_files = (int(len(files)))
        for i in tqdm.tqdm(range(count_files)):
            time.sleep(0.1)
            for fname in files:
                full_fname = os.path.join(root, fname)

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