在Python项目中实时捕获pygtk GUI的STDOUT输出

1 投票
3 回答
1457 浏览
提问于 2025-04-16 17:08

我正在做一个项目,想在一个用python和pygtk做的图形界面中使用命令行工具cdparanoia。我用Glade来开发界面。我试过导入subprocess模块,并使用subprocess.Popen来运行这个工具。虽然这样可以工作,但在这个过程中我的图形界面会卡住(连窗口都不能重新绘制),这对用户体验来说不好。我该怎么做才能避免这种情况呢?我想在窗口上放一个取消按钮,但因为程序“冻结”,这个按钮也没法用。最终,我希望能捕捉到错误信息(就像下面那样,音频信息通过标准输出传给sox),并把它展示在一个gtk.Expander里,样子类似于Synaptic在安装程序时的界面,让用户能实时看到发生的事情。此外,我还想用进度指示器中的文本(如下所示)来构建一个真正的进度指示器小部件。我该怎么做才能让shell实时把信息传回python,而不是等到过程结束后一次性给我所有信息呢?

需要捕捉的实时信息:

Working on me - me - DISK 01.flac
cdparanoia III release 10.2 (September 11, 2008)

Ripping from sector       0 (track  1 [0:00.00])
      to sector  325195 (track 15 [1:56.70])

outputting to stdout

 (== PROGRESS == [>                             | 004727 00 ] == :-) O ==)

这是我到目前为止使用的代码:

        quick = " -Z" if self.quick == True else ""
        command = "cdparanoia -w%s 1- -| sox -t wav - \"%s - %s - DISK %s%s.flac\"" %\
                    (
                        quick,
                        self.book_name.replace(" ", "_"),
                        self.author_name.replace(" ", "_"),
                        "0" if disc < 10 else "", 
                        disc
                    )
        print command
        shell = subprocess.Popen(command, shell=True, executable="/bin/bash",
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE
                                )
        data, err = shell.communicate(command)

谢谢, Narnie

3 个回答

0

这里有两个问题。

第一个问题是,你需要设置一个读取超时时间,这样就不会一直等到子进程完成。

第二个问题是,可能会有一些不必要的缓冲发生。

为了处理第一个问题,如果你想异步地从子进程读取数据,可以试试我的 subProcess 模块,并设置一个超时时间:http://www.pixelbeat.org/libs/subProcess.py。需要注意的是,这个模块比较简单,但也比较旧,而且只适用于 Linux 系统。它是新 Python subprocess 模块的基础,所以如果你需要跨平台使用,还是建议使用那个。

如果你想了解或控制可能发生的额外缓冲,可以查看这个链接:http://www.pixelbeat.org/programming/stdio_buffering/

1

如果你写一个图形界面(GUI)程序,并且这个程序需要从文件中读取数据,那么你需要使用一个调度器,把文件的事件整合进图形界面的事件循环里。关于事件循环的一般介绍,你可以在维基百科上找到。对于Gtk+的具体描述,可以参考这个链接

解决你问题的方法是:使用函数g_io_add_watch,把你的操作整合到主事件循环中。你可以在这里找到一个C语言的例子。Python的用法应该类似。

2

我曾经写过一个Python的命令行工具,它可以运行 wget 命令,还能显示实际的Python控制台输出,功能都很完整。

你需要使用 subprocess.Popen,并且要直接写入 sys.stdout

process = subprocess.Popen(shlex.split(command), stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
complete = False

while True:
  output = process.stdout.read(1)

  if output == '' and process.poll() != None:
    break

  if output != '':
    sys.stdout.write(output)
    sys.stdout.flush()

撰写回答