【问题标题】:How to set up progress bar and also print output from subprocess.Popen如何设置进度条并打印 subprocess.Popen 的输出
【发布时间】:2021-11-18 23:58:22
【问题描述】:

我正在尝试使用进度条来减少使用 python subprocess 时运行 FPGA 构建脚本的冗长性。构建脚本可能需要几个小时才能运行,并且构建输出非常冗长。目前,我打印脚本的输出以供用户查看正在取得的进展,并了解进展情况。我想为用户清理它,因为冗长掩盖了重要的 python 日志,这真的很烦人。

本质上,我希望使用tqdm 来表示进度。我想要的是这个(它不必完全匹配下面的例子):

  • 第一行显示进度指示器,即使进度没有增加,经过的时间仍应更新。理想情况下,这将是一个类似于纺车的进度指示器,但我可以制作一个带步骤的进度条
  • 第二行本质上将构建输出显示为进度通知。这应该与构建的输出同步。我希望它在 subprocess.Popen 完成后消失,但这不是必需的

构建开始时,终端可能如下所示:

INFO: starting build
Running Build [                           ] (0:00:01.2345)
@I: compiling file <x>

完成后,它看起来像这样(注意终端输出行不见了)

INFO: starting build
Running Build [...........................] (6:25:48.6251)
<python continues logging stuff here>

为了确定进度,我将检查subprocess.Popen.stdout 以确定它是否是构建脚本中的一行。这是一些伪代码来显示我正在尝试做的事情:

from subprocess import Popen, PIPE

pb = <initialize progress bar>

lines_in_build_script = [
  "step 1",
  "step 2",
]

p = Popen(cmd, shell=True, stdout=PIPE)
for line in p.stdout:
  pb.update_build_output(line)
  if line in lines_in_build_script:
    pb.increment_progress() # I would prefer if it was just a spinning wheel because looking for line in output is asking for trouble
rc = p.wait()

编辑:tqdm 不是必需的,这只是我知道的一个包。我愿意接受有关如何解决此问题的任何建议。

【问题讨论】:

  • 无法写出完整的答案,但如果您想使用tqdm,我认为tqdm doc 可能对您想要实现的目标有有用的答案;特别检查tqdm.write() 函数,它可以让您在不重叠进度条的情况下将信息写入标准输出。此外,如果您想打印这种“复杂”的输出,tqdm.external_write_mode() 上下文可能很有用。它会暂停进度条,但您只能在更新信息的短时间内暂停它。您可能也想使用回车 (\r)。
  • 看起来很复杂(但也许可能,我不知道)是在进度条的上方和下方显示信息。以上所有信息可能会更容易。
  • 太棒了,感谢您的建议。我试过了,对终端的外观不太满意。我尝试了几个不同的软件包并登陆了 yaspin。为进度微调器创建自己的格式化程序很简单。

标签: python progress-bar tqdm


【解决方案1】:

这是一个使用enlighten 的原始询问的工作示例。 Enlighten 会将进度条保留在终端底部,您可以通过常规方法打印任何您喜欢的内容。请参阅docs 了解如何自定义。

from subprocess import Popen, PIPE

import enlighten


cmd = 'for i in {1..80}; do sleep 0.2; echo $i; [ $((i % 10)) -eq 0 ] && echo step $((i/10)); done'
lines_in_build_script = [
    'step 1\n',
    'step 2\n',
    'step 3\n',
    'step 4\n',
    'step 5\n',
    'step 6\n',
    'step 7\n',
    'step 8\n',
]

manager = enlighten.get_manager()
pb = manager.counter(total=len(lines_in_build_script), desc='Long Process')

p = Popen(cmd, shell=True, stdout=PIPE, text=True)
pb.refresh()
for line in p.stdout:
    print(line, end='')
    if line in lines_in_build_script:
        pb.update()
rc = p.wait()

【讨论】:

    【解决方案2】:

    我对@9​​87654322@ 或alive_progress 功能不满意。我找到了yaspin 并创建了一个自定义的“标题”格式化程序,因为根据我用来确定进度的步骤来设置包的百分比是不可能的。相反,我向 yaspin 格式化程序添加了一个计数器以及一些手动更新文本的方法。不完全是我想要的,但它确实简化了标准输出:

    class BarFormat:
        def __init__(self, title: str, total=None, width=None):
            self.title = title
            self._total = total
            self._cnt = 0
            self._start = datetime.datetime.now()
            self._status = ""
            self._width = width if width else os.get_terminal_size(0)[0]
    
        def __str__(self):
            delta = datetime.datetime.now() - self._start
            if self._total:
                text = f"{self.title} ({self._cnt}/{self._total} elapsed {delta}) {self._status}"
            else:
                text = f"{self.title} (elapsed {delta}) {self._status}"
            # resize if needed
            return text[:self._width-10]
    
        def current(self) -> int:
            return self._cnt
    
        def incr(self) -> None:
            self._cnt += 1
    
        def update(self, status: str) -> None:
            self._status = status.strip()
    

    然后在我的代码中,我有这样的东西:

            fmt = BarFormat(bar_title, total=len(bar_items))
            with yaspin(text=fmt).green.arc as bar:
                for line in p.stdout:
                    l = line.decode('utf-8').strip()
                    if l:
                        # update status of bar only if there's output
                        fmt.update(l)
                    if bar_items:
                        if l == bar_items[fmt.current()]:
                            bar.write("> "+l)
                            fmt.incr()
                rc = p.wait()
    
                # final spinner status
                if rc in ignore_rcs:
                    bar.green.ok()
                else:
                    bar.red.fail()
    

    这会产生类似:

    ...
    > compile code
    > <other steps...>
    <spinner> Running Some Tool (125/640 elapsed 0:12:34.56789) @I: some info 
    

    我注意到yaspin 不处理文本字段中超出终端宽度的行。我这样做是为了在实例化类时确定宽度。不过,将其更新为动态会非常容易。

    编辑:我实际上放弃了yaspin。我开始使用rich,因为我决定更新我的一些日志以使用RichHandler,并发现它支持微调器和进度条。这是https://rich.readthedocs.io/en/latest/progress.html的相关信息

    【讨论】:

      【解决方案3】:

      alive-progress的作者在这里!

      实际上,alive-progress 确实支持在其上方放置文本,将它们放在下方没有多大意义,因为终端上的行从上到下运行,因此栏必须留下所有打印的行,就像日志一样。

      今天我发布了最终收据的隐藏,加上已经支持的spinner隐藏,你可以这样做:

      是的,所有这些都有一个很棒的现场旋转器!

      我认为显示经过的时间很酷,但如果您愿意,也可以将其删除,以及“请稍候”消息,如下所示:

      【讨论】:

        猜你喜欢
        • 2020-02-29
        • 2019-12-18
        • 2010-10-17
        • 1970-01-01
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多