Python tarfile如何输出进度?

21

我正在使用以下代码来提取一个tar文件:

import tarfile
tar = tarfile.open("sample.tar.gz")
tar.extractall()
tar.close()

然而,我希望以目前正在提取哪些文件的形式掌握进展情况。我该如何做到这一点?

额外奖励分数:能否创建提取过程的百分比?我想将其用于tkinter更新进度条。谢谢!

7个回答

13

文件进度和全局进度:

import io
import os
import tarfile

def get_file_progress_file_object_class(on_progress):
    class FileProgressFileObject(tarfile.ExFileObject):
        def read(self, size, *args):
            on_progress(self.name, self.position, self.size)
            return tarfile.ExFileObject.read(self, size, *args)
    return FileProgressFileObject

class TestFileProgressFileObject(tarfile.ExFileObject):
    def read(self, size, *args):
        on_progress(self.name, self.position, self.size)
        return tarfile.ExFileObject.read(self, size, *args)

class ProgressFileObject(io.FileIO):
    def __init__(self, path, *args, **kwargs):
        self._total_size = os.path.getsize(path)
        io.FileIO.__init__(self, path, *args, **kwargs)

    def read(self, size):
        print("Overall process: %d of %d" %(self.tell(), self._total_size))
        return io.FileIO.read(self, size)

def on_progress(filename, position, total_size):
    print("%s: %d of %s" %(filename, position, total_size))

tarfile.TarFile.fileobject = get_file_progress_file_object_class(on_progress)
tar = tarfile.open(fileobj=ProgressFileObject("a.tgz"))
tar.extractall()
tar.close()

更具体地说,有没有一种方法在开始提取过程之前获取未压缩的大小? - FLX
@Mike:这算是猴子补丁吗?我认为tarfile.TarFile是该模块的“公共”类(没有下划线),而fileobject是一个“公共”类属性(同样没有下划线),因此您可以安全地使用它们。但是我对Python在这方面的政策并不是很熟悉。 - tokland
@FLX。恐怕使用以上代码无法获得字节粒度的总百分比。你可以有两个进度条:整体进度(文件粒度)和当前文件进度(字节粒度)。 - tokland
@tokland,“TarFile.fileobject”通常是一个固定的全局状态,您可以修改它以更改代码中使用它的行为(并最终为其他人修改它“=p”)。如果不是猴子补丁,那么这就接近了。下划线约定不是Python中内部属性的主要手段,而是文档。我怀疑将其命名为“fileobject”的决定并不是因为实现者认为:“哦,这是一个很好的API,供某人根据自己的需求替换它”。如果是这样,我真的怀疑他们的面向对象设计技能。 - Mike Graham
@Mike,是的,那听起来很合理。我会选择创建一个自定义文件对象的代码,以避免调整tarfile模块。 - tokland
显示剩余3条评论

7
你可以直接使用 tqdm() 函数并打印提取的文件数量的进度:
import tarfile
from tqdm import tqdm

# open your tar.gz file
with tarfile.open(name=path) as tar:

    # Go over each member
    for member in tqdm(iterable=tar.getmembers(), total=len(tar.getmembers())):

        # Extract member
        tar.extract(member=member)

7
你可以在extractall()中指定members参数。
with tarfile.open(<path>, 'r') as tarball:
   tarball.extractall(path=<some path>, members = track_progress(tarball))

def track_progress(members):
   for member in members:
      # this will be the current file being extracted
      yield member

memberTarInfo 对象,查看所有可用的函数和属性请点击此处


3
yield member 后面,你可以打印出名称或更新进度条。 - Xiong Chiamiov
1
这似乎不应该起作用 - members 是 extractall 的输入,而不是输出?我有什么遗漏的吗? - O'Rooney
@O'Rooney,我来晚了,但是没错。这就是为什么我们在那里使用yield的原因。默认情况下会使用简单的for循环,我们的重写意味着在提取过程中我们也可以访问成员列表,缺点是现在确保不漏掉任何成员的责任落在了我们身上。 - Yamirui

3

1
看代码中的“extractall”调用了“extract”,因此不应该有速度惩罚。 - tokland
文档注明:“extract()方法不能解决多个提取问题。在大多数情况下,您应该考虑使用extractall()方法。”但是,由于不知道这些提取问题是什么,我不敢轻易将“extract”替换为“extractall”。 - Xiong Chiamiov

2

那个库离生产就绪还有很远的路要走,例如当没有传递进度函数时使用未分配的变量...将路径字符串传递给extractall会失败,因为它期望一个tarinfo(虽然两个选项都应该是可能的)。 - andsens

1

要查看当前正在提取的文件,以下方法适用:

import tarfile

print "Extracting the contents of sample.tar.gz:"
tar = tarfile.open("sample.tar.gz")

for member_info in tar.getmembers():
    print "- extracting: " + member_info.name
    tar.extract(member_info)

tar.close()

0

这是我使用的方法,无需猴子补丁或需要条目数量。

def iter_tar_files(f):
    total_bytes = os.stat(f).st_size
    with open(f, "rb") as file_obj,\
        tarfile.open(fileobj=file_obj, mode="r:gz") as tar:
        for member in tar.getmembers():
            f = tar.extractfile(member)
            if f is not None:
                content = f.read()
                yield member.path, content
            # This prints something like: 512/1024 = 50.00%
            print(f"{file_obj.tell()} / {total_bytes} = {file_obj.tell()/total_bytes*100:.2f}%")

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