Python中的enumerate()函数如何与tqdm进度条一起在读取文件时使用?

84

当我使用这段代码迭代我的打开文件时,我无法看到tqdm进度条:

        with open(file_path, 'r') as f:
        for i, line in enumerate(tqdm(f)):
            if i >= start and i <= end:
                print("line #: %s" % i)
                for i in tqdm(range(0, line_size, batch_size)):
                    # pause if find a file naed pause at the currend dir
                    re_batch = {}
                    for j in range(batch_size):
                        re_batch[j] = re.search(line, last_span)

在这里使用tqdm的正确方法是什么?


这个答案可能会有所帮助:https://dev59.com/nqjja4cB1Zd3GeqP9FSo#48327944。尝试将您的循环代码编写为一个函数,并使用 map - jpp
我觉得这对我不起作用。 - Wei Wu
5个回答

113

你已经走在正确的轨道上了。你在正确地使用tqdm,但是在使用tqdm时,请不要在循环内部打印每一行代码。你还需要在第一个for循环中使用tqdm而不是其他循环,像这样:

你正在正确的方向上前进,正确使用了tqdm,但在使用tqdm时,不要在循环内部打印每一行代码。此外,您需要在第一个for循环中使用tqdm而不是其他的循环,如下所示:

with open(file_path, 'r') as f:
    for i, line in enumerate(tqdm(f)):
        if i >= start and i <= end:
            for i in range(0, line_size, batch_size):
                # pause if find a file naed pause at the currend dir
                re_batch = {}
                for j in range(batch_size):
                    re_batch[j] = re.search(line, last_span)

关于使用enumerate及其在tqdm中的用法,可以在此处查看。


31

我也遇到了这个问题 - tqdm 没有显示进度条,因为文件对象中的行数没有被提供。

for 循环将迭代每一行,读取直到下一个换行符被遇到。

为了在 tqdm 中添加进度条,你需要先扫描文件并计算行数,然后将它作为 total 传递给 tqdm。

from tqdm import tqdm

num_lines = sum(1 for line in open('myfile.txt','r'))
with open('myfile.txt','r') as f:
    for line in tqdm(f, total=num_lines):
        print(line)

12
我试图在包含所有维基百科文章的文件上做同样的事情。所以我不想在开始处理之前计算总行数。另外,这是一个bz2压缩文件,因此解压后的行长度会高估该迭代中读取的字节数,所以...
with tqdm(total=Path(filepath).stat().st_size) as pbar:
    with bz2.open(filepath) as fin:
        for i, line in enumerate(fin):
            if not i % 1000:
                pbar.update(fin.tell() - pbar.n)
            # do something with the decompressed line
    # Debug-by-print to see the attributes of `pbar`: 
    # print(vars(pbar))

感谢Yohan Kuanke提供的已删除答案。如果版主恢复它,您可以使用我的答案。


1
这样可以得到正确的输出,但我发现对于文件的每一行调用fin.tell() / pbar.update()会极大地减慢迭代速度。使用if i % 100 == 0:条件来更少地更新pbar,使我获得了10倍的加速。 - Ben Page
很棒的想法,@BenPage!我会将你的优化添加到答案中。 - hobs
如果你使用csv模块来读取文件(例如,使用csv_lines=csv.reader(fin)),那么你就不能使用这种技术。当你调用fin.tell()时,会出现错误OSError: telling position disabled by next() call - Eponymous
1
@Eponymous 是的。这段代码是设计用于文件指针的,而不是任意的可迭代对象。你需要在文件流对象周围应用enumerate()包装器和这个for循环中的代码,而不是其他任何对象(比如csv_reader)...即使它是从文件流派生出来的。它可能无法通过文件流对象的所有方法(比如.tell方法)。你需要使用这段代码创建一个生成器,并将该生成器放在csv_reader的括号内,例如csv_reader((... for i, line in enumerate(fin))) - undefined

6
如果您正在读取一个非常大的文件,请尝试以下方法:
from tqdm import tqdm
import os

file_size = os.path.getsize(filename)
lines_read= []
pbar = tqdm.tqdm(total=file_zize, unit="MB")
with open(filename, 'r', encoding='UTF-8') as file:
    while (line := file.readline()):
        lines_read.append(line)
        pbar.update(s.getsizeof(line)-sys.getsizeof('\n'))
pbar.close()

我忽略了您可能想在append(line)之前进行的处理。
编辑:
我将len(line)更改为s.getsizeof(line)-sys.getsizeof('\n'),因为len(line)不是实际读取的字节数的准确表示(请参见其他帖子)。但是,即使这样也不是100%准确的,因为sys.getsizeof(line)不是实际读取的字节长度,但如果文件非常大,则可以使用“足够接近”的hack。
我尝试过在while循环中使用f.tell()并减去文件位置差,但是Python 3.8.10中的非二进制文件f.tell()速度非常慢。
根据下面的链接,我还尝试了在Python 3.10中使用f.tell(),但那仍然非常缓慢。
如果有更好的策略,请随时编辑此答案,但在编辑之前请提供一些性能数字。请记住,在做循环之前计算行数对于非常大的文件是不可接受的,并且完全破坏了显示进度条的目的(例如,尝试使用具有300百万行的30Gb文件)

为什么Python中读取非二进制模式下的文件时,f.tell()很慢 https://bugs.python.org/issue11114


非常感谢,我对如何使用tqdm处理超出内存的大文件感到困惑。 - Iaoceot
如果你从tqdm导入了tqdm,那么请在初始pbar语句中删除其中一个tqdm--即 pbar = tqdm(total=file_zize, unit="MB"). - Barrel Roll

2
在使用readlines()读取文件时,可以采用以下方法:
from tqdm import tqdm
with open(filename) as f:
    sentences = tqdm(f.readlines(),unit='MB')

unit='MB' 可以根据需要更改为 'B'、'KB' 或 'GB'。


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