有没有一种方法可以将进度条(例如tqdm)添加到PyYAML yaml.load()函数?

时间:2018-09-06 22:44:55

标签: python progress-bar pyyaml tqdm

使用PyYAML作为CLoader解析器的YAML,我试图加载YAML文件,进行解析,然后将其写入单独的文件。 / p>

出于测试目的,我使用的是一个很大的YAML文件,大于1GB

我正试图在命令行中显示一个进度条,以显示我的Python脚本正在运行并估计需要多长时间。

这是我当前的代码:

import yaml
import argparse

from tqdm import tqdm
from yaml import CLoader as Loader

def main():

parser = argparse.ArgumentParser(description='Takes in YAML files and uploads straight to Neo4J database')
parser.add_argument('-f', '--files', nargs='+', metavar='', required=True,
                    help='<Required> One or more YAML files to upload')

args = parser.parse_args()

for file_name in args.files:

    with open(file_name, 'r') as stream:
        print("Reading input file...")
        with open('test2.txt', 'w') as wf:
            print("Writing to output file...")

            try:
                for data in tqdm(yaml.load(stream, Loader=Loader)):
                    wf.write(data.get('primaryName') + '\n')
                    wf.write('++++++++++\n')
            except yaml.YAMLError as exc:
                print(exc)

if __name__ == "__main__":
    main()

现在发生的是,有一个tqdm进度条显示在数据写入循环中,而不是yaml.load()进程,这是花费最多的时间。

也就是说,很长一段时间,YAML文件完全加载之前,没有进度条显示。

我希望找到一种解决方案,以便能够将进度条包装在我无法访问的功能(在这种情况下为yaml.load())周围。

我做错什么了吗?任何建议都将是很棒并且值得赞赏的。

1 个答案:

答案 0 :(得分:0)

否,无法将进度条包裹在您无法访问的代码周围。

此外,当您遍历可迭代对象(不在这里)时,只能使用iterable-based接口连接tqdm。因此,您必须使用基于update的界面:

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

问题是,如何让PyYAML调用该pbar.update

理想情况下,您想找到一个挂接加载过程的位置,您可以在其中调用<{1>}。如果这不可能,则您将不得不做一些丑陋的事情(例如fork pbar.update并添加到其API,或者在运行时通过monkeypatching进行相同的操作),或切换到其他库。但这应该是可能的。


最明显的选择是创建自己的PyYAML子类。 PyYAML的文档解释了此类的API,因此您可以覆盖那里的任何方法以发出一些进展,然后PyYAML.Loader到基类。

但是不幸的是,他们都没有那么有前途。当然,您可以为每个令牌,每个事件或每个节点调用一次,但是在不知道有多少令牌,事件或节点的情况下,这无法让您知道到文件的距离。如果您想要一个不确定的进度微调器,那很好,但是如果您可以获取实际进度,并估计需要走多长时间,依此类推,那总会更好。

可以可以做的一件事是在其super上进行Loader子类调用tell,以计算出到目前为止已读取了多少字节。

我在这台计算机上没有PyYAML,而且文档非常混乱,因此您可能需要进行一些实验,但这应该是这样的:

stream

但是我不知道如何获取PyYAML来将回调传递到class ProgressLoader(yaml.CLoader): def __init__(self, stream, callback): super().__init__(stream) # __ because who knows what names the base class is using? self.__stream = stream self.__pos = 0 self.__callback = callback def get_token(self): result = super().get_token() pos = self.__stream.tell() self.__callback(pos - self.__pos) self.__pos = pos return result 构造函数中,因此您必须执行以下操作:

ProgressLoader

但是无论如何,只要我们转到文件,不搞乱记录在案的加载器类型,而只写一个文件包装器,可能会更容易。

The docs for file objects相当密集,但是至少它们很清楚-实际工作非常简单:

with open(file_name, 'r') as stream:
    size = os.stat(stream.fileno()).st_size
    with tqdm(total=size) as progress:
        factory = lambda stream: ProgressLoader(stream, progress.update)
        data = yaml.load(stream, Loader=factory)

现在:

class ProgressFileWrapper(io.TextIOBase):
    def __init__(self, file, callback):
        self.file = file
        self.callback = callback
    def read(self, size=-1):
        buf = self.file.read(size)
        if buf:
            self.callback(len(buf))
        return buf
    def readline(self, size=-1):
        buf = self.file.readline(size)
        if buf:
            self.callback(len(buf))
        return buf

这当然不是完美的。我们在这里假设所有工作都是从磁盘读取文件,而不是对其进行解析。可能足够接近真实,我们才能摆脱它,但是如果不是,您将拥有其中一个进度条,可以将其压缩到几乎100%,然后长时间呆在那里。 1


1。这不仅令人讨厌,而且与Windows和其他Microsoft产品紧密相关,以至于他们可能起诉您窃取其外观和感觉。 :)